Migrate from Amazon SES
The provider-specific half of the migration guide: how the SES v2 SendEmail call, account-level suppression list, and SNS-based event notifications map here. Do the steps in the main guide in order. This page is the lookup table for steps 1, 3, and 4.
Map the send call
SES splits a send across Destination, Content, and configuration-set plumbing. Our POST /v1/email/messages is one flat payload:
| What it does | SES (SendEmail v2) | Bird |
|---|---|---|
| Sender | FromEmailAddress | from |
| Recipients | Destination.*Addresses | to / cc / bcc (arrays) |
| Subject | Content.Simple.Subject | subject |
| Body | Content.Simple.Body.Html/Text | html / text (at least one) |
| Reply-to | ReplyToAddresses | reply_to (array) |
| Custom headers | Content.Simple.Headers | headers (string → string object) |
| Filterable labels | EmailTags | tags: {name, value} pairs |
| Round-trip context | (none) | metadata: arbitrary JSON |
| Stored template | Content.Template | template + template.parameters |
| Open/click tracking | configuration set | track_opens / track_clicks (default true) |
| IP pool | dedicated IP pool (config set) | ip_pool_id (ipp_... or ipp_shared) |
| Category | (none) | category: marketing (default) or transactional |
Our field caps and defaults (recipient counts, tag and metadata limits) live in Sending email.
Porting notes:
- Configuration sets dissolve into per-message fields. Tracking, IP pool, and event routing were configuration-set concerns on SES. Here, the first two are payload fields and event routing is a webhook subscription.
- Auth changes from SigV4 to a bearer token. No request signing; a plain Authorization: Bearer bk_... header. Drop the AWS SDK credential chain from this code path.
- SES templates port to stored templates. Content.Template (template name plus TemplateData) maps to our template field with values in template.parameters. See sending with a template.
- Content.Raw (MIME) has no equivalent. We build the message from structured fields. If you assemble raw MIME to attach files, send them as our attachments array instead (base64 content per file, content_id for inline images).
- SES sandbox ≠ Bird sandbox. SES's sandbox restricts who you can send to. Our mail sandbox is a simulator with magic addresses: no allowlisting, and nothing is delivered.
Export suppressions
Export the account-level suppression list and run it through the import loop:
- GET /v2/email/suppressed-destinations (paginate with NextToken, and each entry has BOUNCE or COMPLAINT as the reason)
Translate webhook events
SES publishes events through SNS or EventBridge. We POST signed webhooks directly, so the SNS topic, subscription-confirmation handshake, and message-envelope unwrapping all go away. The event names map like this:
| Outcome | SES | Bird |
|---|---|---|
| Accepted/processed | Send | email.accepted → email.processed |
| Delivered | Delivery | email.delivered |
| Temporary failure | DeliveryDelay | email.deferred |
| Permanent bounce | Bounce | email.bounced / email.out_of_band_bounce |
| Spam complaint | Complaint | email.complained |
| Blocked/suppressed | (none) | email.rejected |
| Open | Open | email.opened |
| Click | Click | email.clicked |
| Unsubscribe | Subscription | email.list_unsubscribed |
email.rejected is new relative to SES: we report suppressed recipients visibly (status rejected, rejection_reason: recipient_suppressed) rather than counting them into the send-and-bounce cycle. Add a handler for it.
In place of SNS message verification, we sign per the Standard Webhooks specification, with HMAC headers on the delivery itself. The verification recipe is in Webhooks & events.
Cut over
Work through domains & DNS and the sandbox smoke test in the main guide. Both are provider-independent. One SES-specific note for the DNS step: SES's "Easy DKIM" CNAMEs stay in place during the transition. Our DKIM TXT record uses its own selector, so the two coexist.
Next steps
- Sending domains: registration, verification lifecycle, and the DNS records you're re-pointing
- Webhooks & events: endpoint setup and Standard Webhooks verification
- Testing sandbox: smoke-test the new integration before cutover
- Suppressions: confirm your imported list and how we maintain it from here