Documentation
Sign inGet started

Migrate from Mandrill

The provider-specific half of the migration guide: how Mandrill's (Mailchimp Transactional) messages/send payload, rejection blacklist, and webhooks map here. Do the steps in the main guide in order. This page is the lookup table for steps 1, 3, and 4.
Two shape changes dominate the port. Mandrill nests everything under a message object and authenticates with a key in the request body. We take a flat top-level payload and a standard Authorization: Bearer header. And Mandrill's recipient type (to/cc/bcc as a field on each address) becomes our separate to/cc/bcc arrays.

Map the send call

What it doesMandrill (messages/send)Bird
Authkey in request bodyAuthorization: Bearer bk_... header
Sendermessage.from_email / from_namefrom: string or { "email", "name" }
Recipientsmessage.to: [{ email, name, type }]to / cc / bcc: split by the type field
Subjectmessage.subjectsubject
Bodymessage.html / message.texthtml / text (at least one)
Reply-tomessage.headers["Reply-To"]reply_to: array
Custom headersmessage.headersheaders: string → string object
Filterable labelsmessage.tags: bare stringstags: { name, value } pairs
Round-trip contextmessage.metadatametadata: arbitrary JSON
Stored templatemessages/send-template + merge_varstemplate + template.parameters
Schedulingsend_atscheduled_at
Open/click trackingmessage.track_opens / track_clickstrack_opens / track_clicks (default true)
Attachmentsmessage.attachments: { type, name, content }attachments: { content_type, filename, content }
Inline imagesmessage.images: { type, name, content }attachments with content_id
Categorysubaccount / tags conventioncategory: marketing (default) or transactional
Our field caps and defaults (recipient counts, tag and metadata limits) live in Sending email.
Porting notes:
  • Flatten and re-auth. Drop the message wrapper (its fields move to the top level) and move the API key out of the body into the Authorization header. The key field has no equivalent here.
  • Split recipients by type. Mandrill marks each recipient to, cc, or bcc on the address object. We use three separate arrays. Bucket the to list by its type field as you port.
  • Tags become name/value pairs. Mandrill tags are bare strings ("welcome"). Our tags are { name, value } pairs. Pick a stable name, for example { "name": "category", "value": "welcome" }, so your filters and analytics group the way your Mandrill stats did. metadata ports across directly as JSON.
  • Stored templates port, with one caveat. Mandrill's messages/send-template becomes our template field (referenced by ID or slug) with values in template.parameters. See sending with a template. Our template variables are per message rather than per recipient, so per-recipient merge_vars become one batch entry per recipient, each with its own parameters.
  • Attachments port directly. Mandrill's base64 content is our content, and inline images (referenced as cid: in the HTML) become attachments entries with content_id. See attachments.

Export suppressions

Mandrill keeps unwanted addresses on its rejection blacklist. Pull it with the rejects/list API (or export from the Rejection Blacklist view). Each entry has a reason (hard-bounce, soft-bounce, spam, unsub, custom). Skip the soft-bounce rows (transient, not a true suppression) and run the rest through the import loop.

Translate webhook events

Mandrill posts batched event arrays; map the event value to Bird's event vocabulary:
OutcomeMandrillBird
Sent / acceptedsendemail.acceptedemail.processed
Delivered(none)email.delivered
Temporary failuredeferralemail.deferred
Permanent bouncehard_bounceemail.bounced / email.out_of_band_bounce
Soft bouncesoft_bounceemail.deferred (then email.bounced if it gives up)
Spam complaintspamemail.complained
Unsubscribeunsubemail.unsubscribed / email.list_unsubscribed
Rejected/blockedrejectemail.rejected
Openopenemail.opened
Clickclickemail.clicked
Two differences to code for:
  • We report delivery explicitly. Mandrill's send event means the message was injected. We split acceptance (email.accepted/email.processed) from the recipient mail server taking it (email.delivered).
  • Events are recipient-scoped and signed differently. Our delivery events have recipient_id alongside email_id (one stream per recipient), and we deliver one event per request, signed per the Standard Webhooks spec rather than Mandrill's X-Mandrill-Signature HMAC over batched arrays. See Webhooks & events for verification.

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 we maintain it from here