Migrate from Resend
The provider-specific half of the migration guide: how Resend's POST /emails payload, suppression handling, and Svix-signed webhooks map onto Bird. Do the steps in the main guide in order — this page is the lookup table for steps 1, 3, and 4. Of the providers covered, this is the shortest port: the payloads are nearly field-for-field identical.
Map the send call
| What it does | Resend | Bird |
|---|---|---|
| Sender | from | from |
| Recipients | to / cc / bcc (max 50) | to / cc / bcc (arrays, max 50 each) |
| Subject | subject | subject |
| Body | html / text | html / text (at least one) |
| Reply-to | reply_to | reply_to (array, 1–25) |
| Custom headers | headers | headers (string → string object) |
| Filterable labels | tags — {name, value} pairs | tags — {name, value} pairs, max 20 |
| Round-trip context | — (tags double as context) | metadata — arbitrary JSON, max 2 KB |
| Open/click tracking | per-domain dashboard setting | track_opens / track_clicks (default true) |
| Category | — | category: transactional (default) or marketing |
Porting notes:
- Tags keep their shape, and metadata is an upgrade. Resend tags are the same {name, value} pairs Bird uses, but they carry value constraints that pushed correlation data into tag values; on Bird, move correlation context into metadata (arbitrary JSON, read back via GET /v1/email/messages/{email_id}) and keep tags for filtering.
- Tracking moves into the payload. Resend toggles open/click tracking per domain in the dashboard; Bird sets track_opens/track_clicks per message (both default true).
- attachments and scheduled_at are reserved on Bird and return 422 unsupported_feature today — if you use either on Resend, plan around the gap before cutover. The same goes 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.
- Batch sending ports directly — Resend's POST /emails/batch becomes Bird's batch endpoint, with per-entry results in both cases.
Export suppressions
Resend doesn't expose a dedicated suppression-list export. Pull the addresses whose last event is bounced or complained — from the Emails view in the dashboard, or by walking your stored webhook events if you've been recording them — and run the list through the import loop. If you use Audiences for marketing mail, also carry over contacts marked unsubscribed.
Translate webhook events
| Outcome | Resend | Bird |
|---|---|---|
| Accepted/processed | email.sent | email.accepted → email.processed |
| Delivered | email.delivered | email.delivered |
| Temporary failure | email.delivery_delayed | email.deferred |
| Permanent bounce | email.bounced | email.bounced / email.out_of_band_bounce |
| Spam complaint | email.complained | email.complained |
| Blocked/suppressed | email.failed | email.rejected |
| Open | email.opened | email.opened |
| Click | email.clicked | email.clicked |
| Unsubscribe | — | email.list_unsubscribed |
The signing scheme barely changes: Resend delivers through Svix (svix-id, svix-timestamp, svix-signature headers), and Bird signs per the Standard Webhooks specification that uses the same HMAC construction with webhook-* header names. If you verify Resend deliveries today, the same code verifies Bird's after renaming the three headers — or use the recipe in Webhooks & events.
One behavioral difference: Resend's events are message-scoped; Bird's delivery events are recipient-scoped (recipient_id alongside email_id), so a three-recipient send produces three delivery outcomes, not one.
Cut over
Work through domains & DNS and the sandbox smoke test in the main guide — both are provider-independent.