Sign inGet started

Migrate from Resend

This page maps Resend's POST /emails payload, suppression handling, and Svix-signed webhooks to Bird. Follow the main migration guide in order, and use these mappings for steps 1, 3, and 4. The send payloads have similar fields, but tracking, metadata, and webhook verification require changes.

Hand this to your agent

Paste this into Claude Code, Cursor, or Codex. The agent works through this page against your own repository, using whichever Bird surface it already has: the MCP server if one is connected, the CLI if it is installed and signed in.
Exemple de code
I am moving an email integration from Resend to Bird. Route through it with me.
1. Check what you already have before setting anything up. If Bird's MCP server is connected, use its tools. If the Bird CLI is installed and signed in, use that. Either one is enough, and every step below is an action you take with whichever you have. Only if neither is present, follow https://bird.com/docs/ai/set-up-your-agent.md to set one up and sign me in. Every Bird docs page serves Markdown at its own URL with `.md` appended, so fetch that rather than the HTML.
2. Read https://bird.com/docs/guides/email/migrate/resend.md for the payload, suppression and event mapping, and https://bird.com/docs/guides/email/migrate.md for the order the steps go in.
3. Find and list my Resend usage in this repository before you change anything: the POST /emails and batch call sites and any SDK wrappers around them, the webhook handler and the URL it is registered at, and every domain I send from.
4. Register each of those sending domains with Bird and give me the DNS records to publish, following https://bird.com/docs/guides/email/sending-domains.md. Leave every DNS record my current provider uses exactly as it is: Bird's records are published alongside them and both providers authenticate side by side until I switch traffic. Publishing DNS affects mail for the whole domain, so show me the records and let me publish them.
5. Rebuild my suppression list and import it into Bird before any production traffic goes through Bird, so my first sends do not reach addresses that already bounced or complained. Resend publishes no suppression export, so there is no endpoint that returns this list: derive it from whatever bounce and complaint events I have stored from my webhook, from the dashboard's Emails view, and from contacts marked unsubscribed in any Audience I use. That means the list can be incomplete without either of us noticing, so show me the list you built and tell me which of those sources each address came from before you import anything. The Bird import takes one address per request and is idempotent, so a partial re-run is safe. https://bird.com/docs/guides/email/suppressions.md has the reason taxonomy.
6. Port the send call and the webhook handler using the mapping tables on the provider page. Verification is a header change rather than a rewrite here: Resend signs with Svix and Bird signs per Standard Webhooks, which is the same HMAC construction with the svix-* headers renamed to webhook-*, so keep my verifier and rename what it reads. The event shape does change: Resend's events are scoped to a message and Bird's are scoped to a recipient, so a send to three recipients yields three outcomes rather than one. See https://bird.com/docs/guides/webhooks.md and https://bird.com/docs/guides/email/events.md.
7. Run my whole integration against Bird's mail sandbox before any production traffic, following https://bird.com/docs/guides/email/testing-sandbox.md. Sandbox sends run the real pipeline without reaching an inbox or touching my sending reputation.
8. Stop and ask me wherever a step needs a decision. Do not point production traffic at Bird until I have seen the sandbox results and replied with the words cut over to Bird. Retiring the Resend path is a separate step that comes later: ask me again and wait for me to reply with the words retire the Resend path. A reply that agrees without naming what it is authorising is not authorisation. Finish by telling me what is left that only a person can do.

Map the send call

What it doesResendBird
Senderfromfrom
Recipientsto / cc / bccto / cc / bcc (arrays)
Subjectsubjectsubject
Bodyhtml / texthtml / text (at least one)
Reply-toreply_toreply_to (array)
Custom headersheadersheaders (string → string object)
Filterable labelstags: {name, value} pairstags: {name, value} pairs
Round-trip context(none; tags double as context)metadata: arbitrary JSON
Schedulingscheduled_atscheduled_at
Open/click trackingper-domain dashboard settingtrack_opens / track_clicks (default true)
Category(none)category: marketing (default) or transactional
Our field caps and defaults (recipient counts, tag and metadata limits) live in Sending email.
Porting notes:
  • Tags keep their shape, and metadata is an upgrade. Resend tags are the same {name, value} pairs we use, but their value constraints pushed correlation data into tag values. Here, move correlation context into metadata (arbitrary JSON, echoed back on every webhook event and returned on API reads) and keep tags for filtering. See tags vs metadata.
  • Tracking moves into the payload. Resend toggles open/click tracking per domain in the dashboard. We set track_opens/track_clicks per message (both default true).
  • scheduled_at maps directly, name and all. See scheduled sending. For react, render your React Email templates to HTML in your application (the render function from @react-email/render works unchanged) and send the result as html.
  • Attachments port directly. Resend's attachments (base64 content) map to our attachments array. Set content_id for inline images.
  • Batch sending ports directly. Resend's POST /emails/batch becomes our batch endpoint, with per-entry results in both cases.

Export suppressions

Resend doesn't expose a dedicated suppression-list export. Pull addresses whose last event is bounced or complained. Use the Emails view in the dashboard or your stored webhook events, then run the list through the import loop. If you use Audiences for marketing mail, also carry over contacts marked unsubscribed.

Translate webhook events

OutcomeResendBird
Accepted/processedemail.sentemail.acceptedemail.processed
Deliveredemail.deliveredemail.delivered
Temporary failureemail.delivery_delayedemail.deferred
Permanent bounceemail.bouncedemail.bounced / email.out_of_band_bounce
Spam complaintemail.complainedemail.complained
Blocked/suppressedemail.failedemail.rejected
Openemail.openedemail.opened
Clickemail.clickedemail.clicked
Unsubscribe(none)email.unsubscribed / email.list_unsubscribed
Webhook verification uses a related HMAC construction but different headers. Resend uses svix-id, svix-timestamp, and svix-signature. Bird follows the Standard Webhooks specification with webhook-* headers. Update your verifier to use Bird's signing secret and the Webhooks and events procedure.
One behavioral difference: Resend's events are message-scoped. Our delivery events are recipient-scoped (recipient_id alongside email_id), so a three-recipient send produces three delivery outcomes, one per recipient.

Cut over

Work through domains & DNS and the sandbox smoke test in the main guide. Both are provider-independent.

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