Documentation
Sign inGet started

Migrate from Mailgun

The provider-specific half of the migration guide: 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:
What it doesMailgunBird
Senderfromfrom
Recipientsto / cc / bccto / cc / bcc (arrays)
Subjectsubjectsubject
Bodyhtml / texthtml / text (at least one)
Reply-toh:Reply-Toreply_to (array)
Custom headersh:X-*headers (string → string object)
Filterable labelso:tagtags: {name, value} pairs
Round-trip contextv:* / X-Mailgun-Variablesmetadata: arbitrary JSON
Stored templatetemplate + t:variablestemplate + template.parameters
Schedulingo:deliverytimescheduled_at
Open/click trackingo:tracking-opens / o:tracking-clickstrack_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.
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, 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. 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.

Export suppressions

Mailgun keeps three per-domain lists; export each and run them through the import loop:
  • 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:
OutcomeMailgunBird
Accepted/processedacceptedemail.acceptedemail.processed
Delivereddeliveredemail.delivered
Temporary failurefailed (temporary)email.deferred
Permanent bouncefailed (permanent)email.bounced / email.out_of_band_bounce
Spam complaintcomplainedemail.complained
Blocked/suppressed(none)email.rejected
Openopenedemail.opened
Clickclickedemail.clicked
Unsubscribeunsubscribedemail.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 specification, with headers rather than payload fields. Swap your verification code for the recipe in Webhooks & events.

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 Bird maintains it from here