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 onto Bird. 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; Bird's 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, max 50 each) |
| Subject | Content.Simple.Subject | subject |
| Body | Content.Simple.Body.Html/Text | html / text (at least one) |
| Reply-to | ReplyToAddresses | reply_to (array, 1–25) |
| Custom headers | Content.Simple.Headers | headers (string → string object) |
| Filterable labels | EmailTags | tags — {name, value} pairs, max 20 |
| Round-trip context | — | metadata — arbitrary JSON, max 2 KB |
| Open/click tracking | configuration set | track_opens / track_clicks (default true) |
| IP pool | dedicated IP pool (config set) | ip_pool (ipp_... or ipp_shared) |
| Category | — | category: transactional (default) or marketing |
Porting notes:
- Configuration sets dissolve into per-message fields. Tracking, IP pool, and event routing were configuration-set concerns on SES; on Bird the first two are payload fields and event routing is a webhook subscription.
- Auth changes from SigV4 to a bearer token. No request signing — Authorization: Bearer bk_.... Drop the AWS SDK credential chain from this code path.
- Content.Raw (MIME) has no equivalent — Bird builds the message from structured fields. If you assemble raw MIME for attachments, note attachments is reserved and returns 422 unsupported_feature today; the full reserved list is in Sending email.
- SES sandbox ≠ Bird sandbox. SES's sandbox restricts who you can send to; Bird's 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; each entry carries BOUNCE or COMPLAINT as the reason)
Translate webhook events
SES publishes events through SNS or EventBridge; Bird POSTs 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 | — | email.rejected |
| Open | Open | email.opened |
| Click | Click | email.clicked |
| Unsubscribe | Subscription | email.list_unsubscribed |
email.rejected is new relative to SES: Bird reports 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, Bird signs per the Standard Webhooks specification — 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 — Bird's DKIM TXT record uses its own selector, so the two coexist.