Skip to content

feat: add Amazon SES email adapter#123

Merged
abnegate merged 2 commits into
mainfrom
feat-ses-email-adapter
Jun 2, 2026
Merged

feat: add Amazon SES email adapter#123
abnegate merged 2 commits into
mainfrom
feat-ses-email-adapter

Conversation

@abnegate
Copy link
Copy Markdown
Member

@abnegate abnegate commented Jun 2, 2026

Add a production AWS SES adapter built for high-volume bulk sending (the 500K-recipient newsletter path), using the SES API v2 directly with no AWS SDK dependency.

Primary path (no attachments) uses SendBulkEmail, which is template-based: a deterministic template name is derived from a hash of subject+body+isHtml, created on demand the first time SES reports it missing, and reused across every batch of the same send. Each recipient maps to one BulkEmailEntry and its BulkEmailEntryResults[].Status is mapped to a per-recipient result.

Fallback path (attachments present) uses SendEmail with Content.Raw, one request per recipient, with the MIME assembled via PHPMailer (matching the SMTP adapter). SES templates cannot carry attachments, so this mirrors how the Resend adapter falls back to individual sends.

Authentication is hand-rolled AWS Signature Version 4 (service "ses"), supporting temporary credentials via an optional session token. The signing core is covered by a deterministic unit test asserting it reproduces AWS's published aws-sig-v4-test-suite "get-vanilla" signature, plus request-routing unit tests (bulk endpoint, template lifecycle, result parsing, 50-recipient limit, Raw attachment fallback) and env-gated live tests.

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

Add a production AWS SES adapter built for high-volume bulk sending (the
500K-recipient newsletter path), using the SES API v2 directly with no AWS
SDK dependency.

Primary path (no attachments) uses SendBulkEmail, which is template-based:
a deterministic template name is derived from a hash of subject+body+isHtml,
created on demand the first time SES reports it missing, and reused across
every batch of the same send. Each recipient maps to one BulkEmailEntry and
its BulkEmailEntryResults[].Status is mapped to a per-recipient result.

Fallback path (attachments present) uses SendEmail with Content.Raw, one
request per recipient, with the MIME assembled via PHPMailer (matching the
SMTP adapter). SES templates cannot carry attachments, so this mirrors how
the Resend adapter falls back to individual sends.

Authentication is hand-rolled AWS Signature Version 4 (service "ses"),
supporting temporary credentials via an optional session token. The signing
core is covered by a deterministic unit test asserting it reproduces AWS's
published aws-sig-v4-test-suite "get-vanilla" signature, plus request-routing
unit tests (bulk endpoint, template lifecycle, result parsing, 50-recipient
limit, Raw attachment fallback) and env-gated live tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 2, 2026 13:42
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 2, 2026

Greptile Summary

This PR introduces a new Amazon SES email adapter (SES.php) using the SES API v2 directly — no AWS SDK dependency. Authentication is hand-rolled AWS Signature Version 4, and the implementation splits sends into a template-based bulk path (SendBulkEmail) for plain messages and a per-recipient raw-MIME path (SendEmail with Content.Raw) for attachments.

  • Bulk path: derives a deterministic SHA-256 content-hash template name, creates the SES template on first use (idempotent; tolerates AlreadyExistsException), and maps per-entry BulkEmailEntryResults status back to per-recipient results; CC/BCC are propagated per BulkEmailEntry.Destination.
  • Raw path: assembles MIME via PHPMailer per recipient (matching the SMTP adapter), enforces the SES 10 MB MIME limit, and includes a fast-fail pre-check on raw attachment size before MIME assembly.
  • Test coverage: three test files cover SigV4 signing against the AWS published test vector, network-free routing assertions (template lifecycle, partial failure, 50-recipient limit, display-name quoting), and env-gated live integration tests.

Confidence Score: 5/5

The adapter is well-scoped and all previously raised concerns have been addressed in this revision.

The SigV4 signing is verified against a published AWS test vector. The bulk and raw send paths correctly propagate CC/BCC, handle per-recipient result mapping, guard against oversized attachments (both pre-MIME and post-build), and fall back gracefully on template-missing errors. The Email class auto-normalizes recipients and ensures replyToEmail/replyToName are always non-empty, so the unconditional addReplyTo call in buildMime is safe. No correctness issues were identified in this revision.

No files require special attention.

Important Files Changed

Filename Overview
src/Utopia/Messaging/Adapter/Email/SES.php New SES email adapter with bulk-send (SendBulkEmail) and raw-MIME (SendEmail with attachments) paths, hand-rolled SigV4 signing, deterministic content-hash template naming, CC/BCC per-entry support, and RFC 5322 display-name quoting — all previously raised issues addressed.
tests/Messaging/Adapter/Email/SESRoutingTest.php Network-free routing tests covering bulk endpoint selection, template lifecycle (create-on-miss / AlreadyExists idempotency), per-entry result parsing, 50-recipient limit, CC/BCC per-entry, display-name quoting, raw attachment fallback path, and over-limit MIME size guard.
tests/Messaging/Adapter/Email/SESSigningTest.php Deterministic SigV4 signing tests against the AWS get-vanilla test vector; verifies header sorting, payload sensitivity, and exact Authorization header format.
tests/Messaging/Adapter/Email/SESTest.php Live integration tests (skipped unless SES env vars are set) covering plain/HTML/reply-to/multi-recipient/attachment sends; correctly handles both flat-string and associative-array recipient formats via the Email class normalizer.
README.md Adds SES to the README usage example and marks it complete in the supported-adapters checklist.

Reviews (2): Last reviewed commit: "fix(SES): address review feedback on bul..." | Re-trigger Greptile

Comment thread src/Utopia/Messaging/Adapter/Email/SES.php
Comment thread src/Utopia/Messaging/Adapter/Email/SES.php
Comment thread src/Utopia/Messaging/Adapter/Email/SES.php
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an Amazon SES (API v2) email adapter to the messaging library, including deterministic AWS SigV4 signing, template-backed bulk sending for high-volume newsletters, and a raw-MIME per-recipient fallback for attachments. This fits into the existing adapter architecture alongside SendGrid/Mailgun/Resend/SMTP.

Changes:

  • Introduce SES adapter with SigV4 signing, bulk SendBulkEmail path, and raw SendEmail fallback for attachments.
  • Add deterministic unit tests for signing and request routing, plus env-gated live SES integration tests.
  • Update README usage examples and provider checklist to include SES.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Utopia/Messaging/Adapter/Email/SES.php New SES adapter implementing bulk + raw send paths and hand-rolled SigV4 signing.
tests/Messaging/Adapter/Email/SESRoutingTest.php Network-free tests covering endpoint routing, template lifecycle, per-recipient result mapping, limits, and session-token signing.
tests/Messaging/Adapter/Email/SESSigningTest.php Deterministic SigV4 test against AWS “get-vanilla” vector.
tests/Messaging/Adapter/Email/SESTest.php Env-gated live integration tests hitting real SES API.
README.md Document SES adapter usage and mark SES as supported.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Utopia/Messaging/Adapter/Email/SES.php
Comment thread src/Utopia/Messaging/Adapter/Email/SES.php
Comment thread src/Utopia/Messaging/Adapter/Email/SES.php
Comment thread src/Utopia/Messaging/Adapter/Email/SES.php
Resolves the review comments on the SES adapter:

- Bulk path now sets CcAddresses/BccAddresses on each BulkEmailEntry
  Destination; previously CC/BCC recipients on attachment-free sends were
  silently dropped.
- formatAddress() wraps display names containing RFC 5322 specials in a
  quoted-string, so a name like "Acme, Inc." no longer produces a malformed
  address that SES rejects with a 400.
- Template names are truncated to SES's 64-character limit; the prefix plus a
  full SHA-256 hex was 71 chars, which failed every CreateEmailTemplate.
- A 2xx SendBulkEmail response without parseable per-recipient results is now
  reported as a failure for all recipients instead of false-positive successes.
- MAX_ATTACHMENT_BYTES is overridden to SES's real 10MB message limit, and the
  assembled MIME size is validated so oversized sends (including base64/MIME
  overhead) fail fast with a clear exception.
- Documents that content-hashed templates are never deleted and accumulate
  toward the per-account template quota.

Adds routing-test coverage for CC/BCC forwarding, address quoting, the
template-name length cap, the unparseable-success failure path, and the MIME
size limit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@abnegate abnegate merged commit 1aca896 into main Jun 2, 2026
3 of 4 checks passed
@abnegate abnegate deleted the feat-ses-email-adapter branch June 2, 2026 14:54
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.

2 participants