# Migrate from Resend

The provider-specific half of the [migration guide](/docs/guides/email/migrate): 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`: `marketing` (default) or `transactional` |

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`).
- **`scheduled_at` maps directly.** If you schedule sends on Resend, Bird supports the same [`scheduled_at`](/docs/guides/email/scheduled-sending) field. 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 Bird's [attachments](/docs/guides/email/attachments) array; set `content_id` for inline images.
- **Batch sending ports directly** — Resend's `POST /emails/batch` becomes Bird's [batch endpoint](/docs/guides/email/sending-bulk), 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](/docs/guides/email/migrate#3-import-suppressions). 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](https://www.standardwebhooks.com) 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](/docs/guides/webhooks).

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](/docs/guides/email/migrate#2-re-point-domains-and-dns) and the [sandbox smoke test](/docs/guides/email/migrate#5-verify-in-the-sandbox-before-cutover) in the main guide — both are provider-independent.

## Next steps

- [Sending domains](/docs/guides/email/sending-domains) — registration, verification lifecycle, and the DNS records you're re-pointing
- [Webhooks & events](/docs/guides/webhooks) — endpoint setup and Standard Webhooks verification
- [Testing sandbox](/docs/guides/email/testing-sandbox) — smoke-test the new integration before cutover
- [Suppressions](/docs/guides/email/suppressions) — confirm your imported list and how Bird maintains it from here