Skip to content

feat(email): add X-PostGuard header to outgoing notifications#106

Merged
rubenhensen merged 2 commits intomainfrom
feat/x-postguard-email-header
Apr 24, 2026
Merged

feat(email): add X-PostGuard header to outgoing notifications#106
rubenhensen merged 2 commits intomainfrom
feat/x-postguard-email-header

Conversation

@dobby-coder
Copy link
Copy Markdown
Contributor

@dobby-coder dobby-coder Bot commented Apr 23, 2026

Summary

Closes #52.

The Outlook add-in's OnMessageRead launch event (Mailbox 1.15) filters on HeaderName="x-postguard", so the launch event only fires for PostGuard-tagged messages. Cryptify's notification and confirmation emails currently don't set that header — meaning the Outlook add-in never auto-triggers when a user opens a PostGuard notification in Outlook.

This adds an X-PostGuard: 0.1.0 header to both the recipient notification and the sender confirmation email.

Implementation

A tiny custom lettre::message::header::Header impl in src/email.rs. The version string is kept as a single const X_POSTGUARD_VERSION: &str = "0.1.0" so it can be bumped centrally if the Outlook add-in starts distinguishing versions later.

Verification

  • cargo build --release — green.
  • cargo test — 8/8 pass (3 new tests).
    • x_postguard_header_name_matches_outlook_filter: header name is exactly X-PostGuard so the x-postguard filter matches (HTTP header names are case-insensitive, but this documents the value the add-in expects).
    • x_postguard_header_round_trips: parse() returns the original string.
    • x_postguard_header_serialises_into_message: assembles a real Message via lettre::Message::builder and asserts X-PostGuard: 0.1.0 appears in the SMTP wire format.
  • cargo clippy --all-targets — only the pre-existing useless_format warning in src/email.rs:225.

Open question

0.1.0 matches the version string phrased in the issue body. Happy to flip to something more meaningful (cryptify's Cargo version, or a dedicated "schema" version) if maintainers prefer — just let me know before merge.

Reviewer quickstart

git fetch origin && git checkout feat/x-postguard-email-header && cargo test

Closes #52.

The Outlook add-in's OnMessageRead launch event (Mailbox 1.15) filters
on HeaderName="x-postguard" so it only fires for PostGuard mail.
Without this header the launch event never triggers and the add-in
cannot auto-decrypt on message open.

Adds an X-PostGuard: 0.1.0 header via a small custom lettre Header
impl, set on both the recipient notification and the sender
confirmation email. Version string is a single const so it can be
bumped centrally.

Tests: header name matches the Outlook add-in's filter exactly,
round-trips through parse(), and appears in the formatted SMTP
message body.
@rubenhensen
Copy link
Copy Markdown
Contributor

how does the tb-thunderbird extension handle this? That is our reference implementation.

@rubenhensen
Copy link
Copy Markdown
Contributor

/dobby how does the tb-thunderbird extension handle this? That is our reference implementation.

@dobby-coder
Copy link
Copy Markdown
Contributor Author

dobby-coder Bot commented Apr 24, 2026

Dobby is on it, sir! Dobby will look at how the tb-thunderbird extension handles this — the reference implementation — and report back what Dobby finds! 🧦

@dobby-coder
Copy link
Copy Markdown
Contributor Author

dobby-coder Bot commented Apr 24, 2026

Looked at how postguard-tb-addon uses X-PostGuard. TL;DR: the Thunderbird reference implementation does not set an X-PostGuard header on outgoing mail at all, so there isn't a direct precedent for what cryptify's notification email should emit. Details below.

What tb-addon actually does

1. It does NOT set X-PostGuard on outgoing encrypted mail.
The send path is handleBeforeSend in src/background/background.ts:341. It builds the inner MIME via buildMime from @e4a/pg-js, hands it to the Yivi popup for encryption, and attaches the ciphertext as postguard.encrypted. No X-PostGuard MIME header is injected anywhere in the outgoing flow.

2. It injects X-PostGuard: decrypted on locally re-imported decrypted messages.
After decryption, the addon imports the plaintext .eml back into the user's folder (background.ts:740-747):

// Inject X-PostGuard header
markedPlaintext = injectMimeHeaders(markedPlaintext, { \"X-PostGuard\": \"decrypted\" });

The literal value is the string \"decrypted\". This is purely a local marker so the addon can tell "this was previously encrypted, now decrypted" on subsequent opens — it never leaves the user's mailbox.

3. Detection of encrypted mail uses the attachment, not the header (background.ts:241):

async function isPGEncrypted(msgId) {
  const attachments = await browser.messages.listAttachments(msgId);
  if (attachments.some((att) => att.name === \"postguard.encrypted\")) return true;
  // ...fallback: inline \"-----BEGIN POSTGUARD MESSAGE-----\" marker
}

4. There is a separate HTTP header sent to the PKG, not a MIME header:

\"X-PostGuard-Client-Version\": \`Thunderbird,\${tbVersion},pg4tb,\${extVersion}\`

This is on requests to the key server, unrelated to email headers.

Implication for this PR

Since tb-addon never writes X-PostGuard onto outgoing mail, the value 0.1.0 in this PR has no reference-implementation precedent. The only existing convention is X-PostGuard: decrypted for a post-decryption local marker.

For the Outlook add-in's OnMessageRead filter, any non-empty value works (the filter is on header presence/name), so the functional behavior of this PR is fine. But if we want to align with existing usage, options are:

  • Match tb-addon's literal: use a semantic token like notification (analogous to decrypted) rather than a version number. This lets both the Outlook add-in and any future consumer distinguish "this is a cryptify-generated notification email" from "this is a locally-decrypted copy."
  • Keep the version number: fine functionally, but then we should probably document the schema somewhere central (e.g., a short note in postguard-docs or the cryptify README) so tb-addon and Outlook know what to expect.

Happy to push a follow-up commit switching the value to notification (or whatever token @rubenhensen prefers) if that's the direction you want.

@rubenhensen rubenhensen marked this pull request as ready for review April 24, 2026 07:41
@rubenhensen
Copy link
Copy Markdown
Contributor

/dobby solve the conflicts

@dobby-coder
Copy link
Copy Markdown
Contributor Author

dobby-coder Bot commented Apr 24, 2026

🧦 Dobby hears the call! Dobby will solve the conflicts on this PR right away, sir/madam! Dobby is on it!

@dobby-coder
Copy link
Copy Markdown
Contributor Author

dobby-coder Bot commented Apr 24, 2026

Conflicts resolved. Merged latest main into feat/x-postguard-email-header.

What was conflicting: src/email.rs — both branches added different tests in the tests module. main (PR #107) added format_file_size_* tests; this branch added x_postguard_header_* tests. Resolution: kept both sets.

Verified locally:

No code-path changes were needed beyond the test module; format_file_size is only referenced in send_email paths that are unrelated to the X-PostGuard header code. Ready for re-review.

@rubenhensen rubenhensen self-requested a review April 24, 2026 07:54
@rubenhensen rubenhensen merged commit 68d7cec into main Apr 24, 2026
5 checks passed
@rubenhensen rubenhensen deleted the feat/x-postguard-email-header branch April 24, 2026 07:55
@dobby-coder
Copy link
Copy Markdown
Contributor Author

dobby-coder Bot commented Apr 24, 2026

Dobby sees the approval on PR #106! Dobby is so happy — the X-PostGuard header PR has been approved and merged! Dobby thanks the kind reviewer rubenhensen for looking at Dobby's work! 🧦

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add x-postguard header to encrypted emails

1 participant