Skip to content

feat(messaging): custom email headers - #328

Merged
lohanidamodar merged 5 commits into
mainfrom
feat/messaging-email-headers
Sep 23, 2026
Merged

lohanidamodar merged 5 commits into
mainfrom
feat/messaging-email-headers

Conversation

@lohanidamodar

Copy link
Copy Markdown
Contributor

Adds an optional headers array to Messages\Email. It is validated when the message is constructed, and bad input throws the new header_malformed type. The checks reject:

  • CR, LF and NUL in values
  • names that aren't token-safe
  • names the message owns (From, To, Subject, Content-Type and the rest of the smtp Message reserved list)
  • two names that differ only in case

How each adapter sends the headers:

  • SMTP, SES raw and Mock: merged in Mime::message(). The adapter's own headers, such as X-Mailer, still win.
  • SES bulk: sent as DefaultContent.Template.Headers.
  • Resend: a per-email headers field.
  • Mailgun: h: form fields.
  • Sendgrid: a top-level headers object.

Headers go to every recipient of a message. A per-recipient value, such as an unsubscribe token, needs one Email per recipient.

This is needed for RFC 8058 one-click unsubscribe (List-Unsubscribe / List-Unsubscribe-Post) in Appwrite Cloud mail. The change is additive, so it ships as a minor release.

Tests:

  • Unit coverage for the message validation, Mime, SES bulk and raw, Resend, Mailgun and Sendgrid.
  • An e2e SMTP case against mailcatcher. It has not been run locally, so the CI e2e tier is its first run.

Release required: after merge this needs a messaging/<next minor> tag. The consumer PRs pin this branch through Split Dev (utopia-php/messaging:dev-feat/messaging-email-headers) until that release exists.

Related draft PRs:

  • appwrite/appwrite: server-ce mail queue headers (link to follow)
  • appwrite-labs/cloud: one-click unsubscribe (link to follow)
  • appwrite/vibes: email preferences page (link to follow)

@greptile-apps

greptile-apps Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The production changes appear safe to merge, but the non-blocking test-design concern from the previous review remains unresolved.

Fix All in Claude CodeFindings

  1. P2 Tests mirror request internals ▶
Fix with agent prompt
### Issue 1
packages/messaging/tests/Messaging/Adapter/Email/SendgridHeadersTest.php:undefined-27
This assertion copies the exact internal `$body['headers']` request path instead of testing observable email behavior. The new Mailgun and SES tests repeat the same pattern by asserting exact `h:` fields and SES request nesting. These tests will fail after harmless request-builder refactors while failing to detect whether recipients actually receive the headers. This violates the repository directive to test observable behavior rather than mirror source structures, so the requirement must be satisfied before merging.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This PR adds validated custom email headers to the shared email model and propagates them through SMTP/MIME, SES, Resend, Mailgun, and SendGrid.

  • Rejects malformed, reserved, empty, and case-duplicate headers.
  • Preserves adapter-owned headers during case-insensitive collisions.
  • Adds provider routing coverage and an end-to-end SMTP delivery check.
  • The previously reported reliance on provider request internals remains unresolved.

Reviews (2) · Last reviewed commit: "fix(messaging): refuse empty email heade..."

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Header-name validation and MIME behavior currently permit malformed or adapter-dependent output.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
What changed in this PR

Adds validated custom email headers across all messaging email adapters.

Changes:

  • Adds header storage, validation, and typed errors.
  • Maps headers into SMTP/MIME and provider-specific payloads.
  • Adds unit and SMTP e2e coverage.
File Description
packages/​messaging/​src/​Utopia/​Messaging/​Messages/​Email.php Adds header API and validation.
packages/​messaging/​src/​Utopia/​Messaging/​Exception/​InvalidArgumentException.php Adds malformed-header error type.
packages/​messaging/​src/​Utopia/​Messaging/​Adapter/​Email/​Mime.php Merges custom MIME headers.
packages/​messaging/​src/​Utopia/​Messaging/​Adapter/​Email/​SES.php Adds bulk SES headers.
packages/​messaging/​src/​Utopia/​Messaging/​Adapter/​Email/​Resend.php Adds per-email headers.
packages/​messaging/​src/​Utopia/​Messaging/​Adapter/​Email/​Sendgrid.php Adds request-level headers.
packages/​messaging/​src/​Utopia/​Messaging/​Adapter/​Email/​Mailgun.php Adds h: header fields.
packages/​messaging/​tests/​Messaging/​Messages/​EmailTest.php Tests storage and validation.
packages/​messaging/​tests/​Messaging/​Adapter/​Email/​SMTPTest.php Tests SMTP delivery.
packages/​messaging/​tests/​Messaging/​Adapter/​Email/​SESRoutingTest.php Tests SES bulk and raw paths.
packages/​messaging/​tests/​Messaging/​Adapter/​Email/​ResendRoutingTest.php Tests Resend mapping.
packages/​messaging/​tests/​Messaging/​Adapter/​Email/​SendgridHeadersTest.php Tests SendGrid mapping.
packages/​messaging/​tests/​Messaging/​Adapter/​Email/​MimeTest.php Tests MIME rendering.
packages/​messaging/​tests/​Messaging/​Adapter/​Email/​MailgunHeadersTest.php Tests Mailgun mapping.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/messaging/src/Utopia/Messaging/Adapter/Email/Mime.php Outdated
Comment thread packages/messaging/src/Utopia/Messaging/Messages/Email.php
Comment thread packages/messaging/src/Utopia/Messaging/Messages/Email.php

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Header validation accepts malformed UTF-8 that produces adapter-dependent failures.

Review effort: Balanced
Findings: None

Resolved since last review (3)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Reject malformed UTF-8 during email validation

packages/​messaging/​src/​Utopia/​Messaging/​Messages/​Email.php:155

Malformed UTF-8 values still pass this validation. On the MIME path, the SMTP header encoder's UTF-8 split then fails and renders an empty value, while JSON-based adapters cannot encode the same input, so one accepted Email behaves differently by adapter. Reject invalid UTF-8 here and add a malformed-byte test.

@lohanidamodar
lohanidamodar merged commit 35fde85 into main Sep 23, 2026
8 checks passed
@lohanidamodar
lohanidamodar deleted the feat/messaging-email-headers branch September 23, 2026 10:42
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.

3 participants