# Migrate from Mailgun

The provider-specific half of the [migration guide](/docs/guides/email/migrate): how Mailgun's `POST /v3/{domain}/messages` parameters, suppression lists, and webhook events 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

Mailgun's form-encoded parameter prefixes (`o:` options, `v:` variables, `h:` headers) all become first-class JSON fields on [`POST /v1/email/messages`](/docs/api/reference/create-email-message):

| What it does        | Mailgun                                  | Bird                                                 |
| ------------------- | ---------------------------------------- | ---------------------------------------------------- |
| Sender              | `from`                                   | `from`                                               |
| Recipients          | `to` / `cc` / `bcc`                      | `to` / `cc` / `bcc` (arrays)                         |
| Subject             | `subject`                                | `subject`                                            |
| Body                | `html` / `text`                          | `html` / `text` (at least one)                       |
| Reply-to            | `h:Reply-To`                             | `reply_to` (array)                                   |
| Custom headers      | `h:X-*`                                  | `headers` (string → string object)                   |
| Filterable labels   | `o:tag`                                  | `tags`: `{name, value}` pairs                        |
| Round-trip context  | `v:*` / `X-Mailgun-Variables`            | `metadata`: arbitrary JSON                           |
| Stored template     | `template` + `t:variables`               | `template` + `template.parameters`                   |
| Scheduling          | `o:deliverytime`                         | `scheduled_at`                                       |
| Open/click tracking | `o:tracking-opens` / `o:tracking-clicks` | `track_opens` / `track_clicks` (default `true`)      |
| Category            | (none)                                   | `category`: `marketing` (default) or `transactional` |

Bird-side field caps and defaults (recipient counts, tag and metadata limits) live in [Sending email](/docs/guides/email/sending-email).

Porting notes:

- **The request becomes JSON.** Mailgun accepts multipart form data; Bird takes a JSON body with `Content-Type: application/json`. This is usually the biggest mechanical change in the port.
- **`v:` variables were echoed in events; Bird `metadata` works the same way.** Bird echoes your `metadata` (and `tags`) on every webhook event alongside `email_id`/`recipient_id`, so your handlers get them back without an extra lookup.
- **Recipient variables don't port one-to-one.** Mailgun's `recipient-variables` personalize many recipients in one call; on Bird that job belongs to the [batch endpoint](/docs/guides/email/sending-bulk), one entry per recipient, each entry carrying its own content or its own `parameters` values for `{{ token }}` substitution.
- **Stored templates port, with one caveat.** Mailgun's `template` parameter maps to Bird's `template` field with values in `template.parameters`; see [sending with a template](/docs/guides/email/sending-email#sending-with-a-template). Template authoring on Bird is in preview and not yet open to every workspace, so until yours has it, render content in your application and send `html`/`text`.
- **Attachments port directly.** Mailgun multipart `attachment` / `inline` files become Bird's `attachments` array with base64 `content` (set `content_id` for inline images). See [attachments](/docs/guides/email/attachments).

## Export suppressions

Mailgun keeps three per-domain lists; export each and run them through the [import loop](/docs/guides/email/migrate#3-import-suppressions):

- `GET /v3/{domain}/bounces`
- `GET /v3/{domain}/complaints`
- `GET /v3/{domain}/unsubscribes`

Repeat per sending domain. Mailgun's lists are domain-scoped, while Bird suppressions are workspace-scoped, so the union of your domains' lists is what you import.

## Translate webhook events

Mailgun signals temporary vs permanent failure with one `failed` event plus a `severity` field; Bird splits them:

| Outcome            | Mailgun              | Bird                                         |
| ------------------ | -------------------- | -------------------------------------------- |
| Accepted/processed | `accepted`           | `email.accepted` → `email.processed`         |
| Delivered          | `delivered`          | `email.delivered`                            |
| Temporary failure  | `failed` (temporary) | `email.deferred`                             |
| Permanent bounce   | `failed` (permanent) | `email.bounced` / `email.out_of_band_bounce` |
| Spam complaint     | `complained`         | `email.complained`                           |
| Blocked/suppressed | (none)               | `email.rejected`                             |
| Open               | `opened`             | `email.opened`                               |
| Click              | `clicked`            | `email.clicked`                              |
| Unsubscribe        | `unsubscribed`       | `email.list_unsubscribed`                    |

`email.rejected` has no Mailgun counterpart: Bird reports suppressed recipients visibly (status `rejected`, `rejection_reason: recipient_suppressed`) instead of silently skipping them. Add a handler for it rather than treating it as a bounce.

Verification also changes: Mailgun signs with an HMAC over `timestamp + token` inside the payload's `signature` object, while Bird signs per the [Standard Webhooks](https://www.standardwebhooks.com) specification, with headers rather than payload fields. Swap your verification code for the recipe in [Webhooks & events](/docs/guides/webhooks).

## 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