# Migrate from another provider

This guide walks you through moving production email to Bird. The shape of the work is the same regardless of where you're coming from: one send endpoint to re-map, four DNS records to publish, a suppression list to carry over, a webhook vocabulary to translate, and a sandbox to verify everything against before you flip traffic.

The migration checklist:

1. [Map your send call](#1-map-the-send-call) to `POST /v1/email/messages`
2. [Re-point your sending domains and DNS](#2-re-point-domains-and-dns)
3. [Import your suppression list](#3-import-suppressions)
4. [Switch webhooks to Bird's event vocabulary](#4-switch-webhooks)
5. [Verify against the mail sandbox](#5-verify-in-the-sandbox-before-cutover) before cutover

Steps 1, 3, and 4 depend on which provider you're leaving. Each provider guide carries the field-by-field payload mapping, where to export your suppression list, and the webhook event-name translation table:

- [Migrate from SendGrid](/docs/guides/email/migrate/sendgrid)
- [Migrate from Mailgun](/docs/guides/email/migrate/mailgun)
- [Migrate from Amazon SES](/docs/guides/email/migrate/ses)
- [Migrate from Resend](/docs/guides/email/migrate/resend)
- [Migrate from Mailjet](/docs/guides/email/migrate/mailjet)
- [Migrate from Mandrill](/docs/guides/email/migrate/mandrill)

## 1. Map the send call

Bird has one single-send endpoint, [`POST /v1/email/messages`](/docs/api/reference/create-email-message). You build a flat JSON payload — no `personalizations` wrapper, no MIME assembly — with `from`, `to`/`cc`/`bcc` (up to 50 each), `subject`, `html` and/or `text`, optional `reply_to` (an array, 1–25 entries), and `headers` for custom email headers. A successful send returns `202 Accepted` with an `em_`-prefixed message ID; delivery outcomes arrive asynchronously through webhooks and the read endpoints. The full payload reference is in [Sending email](/docs/guides/email/sending-email); the field-by-field mapping from your current payload is in your [provider guide](#migrating-from-a-specific-provider).

The distinction between `tags` and `metadata` matters more on Bird than the equivalents did at your old provider: tags are first-class filter dimensions (message list, analytics, dashboard rollups), while metadata is opaque round-trip context — stored on the message, returned on API reads, and echoed on every webhook event (alongside the `email_id`/`recipient_id` correlation IDs), so it comes back without an extra lookup. The rule of thumb and limits are in [Tags vs metadata](/docs/guides/email/sending-email#tags-vs-metadata).

**Bird differences worth flagging before you port code:**

- **Scheduling and templates are supported; a few fields are still reserved.** If your current integration schedules sends, Bird supports [scheduled sending](/docs/guides/email/scheduled-sending) via `scheduled_at`, and stored templates via `template`. A few other request fields (`contact_id`, `topic_id`, `in_reply_to_message_id`) remain reserved and return `422 unsupported_feature`; the full list is in [Sending email](/docs/guides/email/sending-email#reserved-fields). Attachments are supported too — port them to Bird's [attachments](/docs/guides/email/attachments) array.
- **Suppressed recipients are rejected, visibly.** A suppressed address still gets a `recipient_id` and appears in the message's recipient list with status `rejected` and an `email.rejected` event (`rejection_reason: recipient_suppressed`) — never a silent drop. Even when every recipient is suppressed, the request is still accepted with a `202`; each recipient just comes back rejected. See [Suppressions](/docs/guides/email/suppressions).
- **Set `category: "transactional"` for operational mail.** A send defaults to `marketing`, and the category controls suppression policy: `marketing` blocks on complaints and unsubscribes, `transactional` delivers through them. Newsletters and campaigns are handled correctly by the default; mark receipts, password resets, and similar operational mail `transactional` so they are not gated by an unsubscribe.

## 2. Re-point domains and DNS

Register each sending domain with [`POST /v1/email/domains`](/docs/api) or on the [**Domains**](https://bird.com/dashboard/w/email/domains) page in the dashboard, then publish the records from the response's `dns_records` array — the [DNS record splitter](/tools/dns-record-splitter) can break the long DKIM value into quoted strings if your provider requires that. Bird asks for four records — and notably **not** the one most providers make you start with:

| Record      | Type  | Gates sending                                                                         |
| ----------- | ----- | ------------------------------------------------------------------------------------- |
| DKIM        | TXT   | Yes — per-organization selector and key, generated for you                            |
| Return-path | CNAME | Yes — routes bounces to Bird and provides SPF alignment                               |
| DMARC       | TXT   | Yes — any valid policy; an existing record (or one at a parent domain) already counts |
| Tracking    | CNAME | No — only gates branded open/click tracking                                           |

The send gate is **DKIM + return-path + DMARC** — if you don't have a DMARC record yet, the [DMARC policy generator](/tools/dmarc-policy) will write one for you. There is no SPF record to publish at your domain apex: SPF is evaluated against the envelope-from domain, which the return-path CNAME points at Bird's bounce infrastructure, so SPF passes and aligns without touching your apex. If your old provider had you add an `include:` to your apex SPF record, you can leave it in place during the transition and remove it after cutover — it neither helps nor hurts your Bird mail, and removing it frees one of the 10 DNS lookups apex SPF allows. The full explanation is in [DKIM, SPF & DMARC](/docs/guides/email/dkim-spf-dmarc#wheres-spf).

Verification is automatic — Bird polls your DNS starting within seconds of registration, and most domains verify within minutes of the records propagating. Each Bird region (`us1`, `eu1`) is independent, so register the domain in every region you send from. Domain lifecycle details are in [Sending domains](/docs/guides/email/sending-domains).

You can publish Bird's records while your old provider's records are still live: the DKIM record uses a Bird-specific selector, the return-path and tracking CNAMEs are new hostnames you choose, and your existing DMARC record satisfies the gate as-is — so both providers authenticate side by side until you're ready to switch traffic.

## 3. Import suppressions

Carry your suppression list over **before** sending production traffic through Bird — otherwise your first sends go to addresses that already bounced or complained at your old provider, which damages the reputation you're trying to protect.

Export the list from your current provider (your [provider guide](#migrating-from-a-specific-provider) has the exact endpoints), then add each address to Bird with [`POST /v1/email/suppressions`](/docs/api/reference/list-suppressions):

```bash
while read -r address; do
  curl -s -X POST https://us1.platform.bird.com/v1/email/suppressions \
    -H "Authorization: Bearer $BIRD_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"email\": \"$address\"}"
done < suppressions.txt
```

Two things to know about this import path:

- **There is no bulk-import endpoint yet.** The v1 suppressions API is single-entry CRUD — one address per `POST` — so importing a large list means looping, as above. Bulk import is deferred to a later release; until then, the loop is the supported path. The call is idempotent (`201` for a new record, `200` with the existing record if the address is already manually suppressed), so re-running a partial import is safe.
- **Imported addresses get `reason: manual`, `applies_to: all`** — they block every category, including transactional. That is stricter than a complaint or unsubscribe record would be natively (those block only non-transactional sends), so if you need the category-aware behavior for specific addresses, see the reason taxonomy in [Suppressions](/docs/guides/email/suppressions#the-four-reasons-and-what-they-block).

Going forward you don't manage bounces yourself: Bird auto-suppresses hard bounces, complaints, and unsubscribes, and fires `email_suppression.created` so your systems can mirror the list.

## 4. Switch webhooks

Register an endpoint with [`POST /v1/webhooks`](/docs/api/reference/create-webhook) and subscribe it to an explicit list of event types. Bird's event names follow `resource.action` — `email.accepted` → `email.processed` → `email.delivered` on the happy path, with `email.deferred`, `email.bounced`, `email.complained`, `email.rejected`, `email.opened`, `email.clicked`, and the unsubscribe pair covering the rest. The event-name translation from your current provider's vocabulary is in your [provider guide](#migrating-from-a-specific-provider); per-event payload schemas are in the [events reference](/docs/guides/email/events).

Every webhook payload carries the correlation IDs — `email_id`, `recipient_id`, and `workspace_id` — so you can tie any event back to your send; `tags` and `metadata` are **not** echoed in the payload. If your current handler relies on payload echo for correlation, the pattern ports to Bird as: store your context in `metadata` on the send, then read it back with `GET /v1/email/messages/{email_id}` when an event needs it — payload echo becomes an API lookup.

Bird signs deliveries per the [Standard Webhooks](https://www.standardwebhooks.com) specification — `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers with an HMAC-SHA256 over `{id}.{timestamp}.{raw body}`. If you already verify Standard Webhooks deliveries from another platform, the exact same verification code works for Bird; otherwise the verification recipe, retry schedule, and replay tooling are in [Webhooks & events](/docs/guides/webhooks). Deliveries are at-least-once and unordered, so deduplicate on `webhook-id` and sort by the payload `timestamp` — the same discipline your current handler should already have.

## 5. Verify in the sandbox before cutover

Before you move production traffic, run your full integration — the ported send call, your webhook handler, your suppression mirroring — against the [mail sandbox](/docs/guides/email/testing-sandbox). Sandbox sends go to magic addresses at `messagebird.dev` and run through the real production pipeline (same `202`, same event sequence, same signed webhook deliveries) without ever reaching an inbox or touching your reputation.

A minimal pre-cutover smoke test:

1. Send to `delivered@messagebird.dev` and assert your handler processes `email.accepted` → `email.processed` → `email.delivered`.
2. Send to `bounce@messagebird.dev` and assert your bounce handling fires on `email.bounced` (simulated bounces don't write to your suppression list, so the address stays reusable).
3. Send to `suppressed@messagebird.dev` and assert you handle a lone `email.rejected` — the shape every suppressed recipient produces in production (the `rejection_reason: recipient_suppressed` detail is on the recipient record and the events API).
4. Send a `category: "marketing"` message and confirm the category shows up where you expect in events and reads.

Use `+label` subaddressing (`bounce+cutover-test@messagebird.dev`) to correlate test cases — the full address appears in your events. Once the smoke test passes, cut traffic over: point your application at Bird, keep the old provider's DNS in place until your Bird domains show `capabilities.sending` verified, and watch the first hours of real deliveries in the dashboard and your webhook stream.

## Migrating from a specific provider

- [SendGrid](/docs/guides/email/migrate/sendgrid) — `personalizations` → flat payload, `categories`/`custom_args` → `tags`/`metadata`, the `dropped` ↔ `email.rejected` equivalence
- [Mailgun](/docs/guides/email/migrate/mailgun) — `o:*`/`v:*`/`h:*` parameters → first-class fields, bounces/complaints/unsubscribes export
- [Amazon SES](/docs/guides/email/migrate/ses) — SendEmail v2 → one endpoint, configuration sets → per-message tracking flags, SNS → signed webhooks
- [Resend](/docs/guides/email/migrate/resend) — near-identical payload shape, Svix-signed webhooks → Standard Webhooks

## Next steps

- [Sending email](/docs/guides/email/sending-email) — the full send payload, tags vs metadata, the async 202 model
- [Sending domains](/docs/guides/email/sending-domains) — registration, verification lifecycle, multi-region setup
- [DKIM, SPF & DMARC](/docs/guides/email/dkim-spf-dmarc) — what each record proves, and why apex SPF isn't required
- [Suppressions](/docs/guides/email/suppressions) — reasons, categories, and the management API
- [Webhooks & events](/docs/guides/webhooks) — endpoint setup, Standard Webhooks verification, retries and replay
- [Events](/docs/guides/email/events) — per-event payload schemas
- [Testing sandbox](/docs/guides/email/testing-sandbox) — the full magic-address list and walkthroughs
- [API reference](/docs/api/reference/create-email-message) — request and response schemas for the send endpoint