Migrate from Mandrill
The provider-specific half of the migration guide: how Mandrill's (Mailchimp Transactional) messages/send payload, rejection blacklist, and 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.
Two shape changes dominate the port. Mandrill nests everything under a message object and authenticates with a key in the request body; Bird takes 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 Bird's separate to/cc/bcc arrays.
Map the send call
| What it does | Mandrill (messages/send) | Bird |
|---|---|---|
| Auth | key in request body | Authorization: Bearer bk_... header |
| Sender | message.from_email / from_name | from: string or { "email", "name" } |
| Recipients | message.to: [{ email, name, type }] | to / cc / bcc: split by the type field |
| Subject | message.subject | subject |
| Body | message.html / message.text | html / text (at least one) |
| Reply-to | message.headers["Reply-To"] | reply_to: array |
| Custom headers | message.headers | headers: string → string object |
| Filterable labels | message.tags: bare strings | tags: { name, value } pairs |
| Round-trip context | message.metadata | metadata: arbitrary JSON |
| Stored template | messages/send-template + merge_vars | template + template.parameters |
| Scheduling | send_at | scheduled_at |
| Open/click tracking | message.track_opens / track_clicks | track_opens / track_clicks (default true) |
| Attachments | message.attachments: { type, name, content } | attachments: { content_type, filename, content } |
| Inline images | message.images: { type, name, content } | attachments with content_id |
| Category | subaccount / tags convention | category: marketing (default) or transactional |
Bird-side 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 Bird equivalent.
- Split recipients by type. Mandrill marks each recipient to, cc, or bcc on the address object; Bird uses 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"); Bird 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 two caveats. Mandrill's messages/send-template becomes Bird's template field (referenced by ID or name) with values in template.parameters; see sending with a template. Bird's 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. And template authoring on Bird is in preview and not yet open to every workspace, so until yours has it, render the final HTML in your application and send it as html.
- Attachments port directly. Mandrill's base64 content is Bird's 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 carries 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:
| Outcome | Mandrill | Bird |
|---|---|---|
| Sent / accepted | send | email.accepted → email.processed |
| Delivered | (none) | email.delivered |
| Temporary failure | deferral | email.deferred |
| Permanent bounce | hard_bounce | email.bounced / email.out_of_band_bounce |
| Soft bounce | soft_bounce | email.deferred (then email.bounced if it gives up) |
| Spam complaint | spam | email.complained |
| Unsubscribe | unsub | email.unsubscribed / email.list_unsubscribed |
| Rejected/blocked | reject | email.rejected |
| Open | open | email.opened |
| Click | click | email.clicked |
Two differences to code for:
- Bird reports delivery explicitly. Mandrill's send event means the message was injected; Bird splits acceptance (email.accepted/email.processed) from the recipient mail server taking it (email.delivered).
- Events are recipient-scoped and signed differently. Bird's delivery events carry recipient_id alongside email_id (one stream per recipient), and Bird delivers 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 Bird maintains it from here