Migrate from Mailchimp Transactional (Mandrill) to emailr_
Move from Mailchimp Transactional (Mandrill) to emailr_: map templates, merge vars, tags, metadata, rejection lists and webhooks without downtime.
Mailchimp Transactional, formerly Mandrill, ties transactional email to a Mailchimp account. emailr_ runs transactional email, marketing broadcasts and shared inboxes on one platform, so you can move both halves of your email at your own pace.
This guide covers the @mailchimp/mailchimp_transactional client, merge vars, templates, rejection lists and webhooks.
The code change
A typical Mailchimp Transactional send call next to its emailr_ equivalent.
import mailchimpFactory from '@mailchimp/mailchimp_transactional'
const mailchimp = mailchimpFactory(process.env.MANDRILL_API_KEY!)
await mailchimp.messages.send({
message: {
from_email: 'hello@mail.acme.com',
to: [{ email: 'user@example.com', type: 'to' }],
subject: 'Your receipt',
html: '<p>Thanks for your order.</p>',
tags: ['receipts'],
metadata: { order_id: '123' },
},
})import { Emailr } from 'emailr'
const emailr = new Emailr({ apiKey: process.env.EMAILR_API_KEY! })
await emailr.emails.send({
from: 'hello@mail.acme.com',
to: 'user@example.com',
subject: 'Your receipt',
html: '<p>Thanks for your order.</p>',
tags: { category: 'receipts', order_id: '123' },
})Mailchimp Transactional concepts in emailr_
Use this table to find where each feature you rely on lives in emailr_.
| Mailchimp Transactional | emailr_ |
|---|---|
| Mandrill API key | API key (et_live_ prefix) |
| Sending domains (DKIM + SPF) | Domain (DKIM TXT, SPF, send. MAIL FROM) |
to: [{ email, type }] | to, cc, bcc fields |
Templates + template_content / merge vars | Templates (template_id + template_data) |
tags array + metadata | tags object |
send_at | scheduled_at (ISO 8601) |
| Rejection denylist | Contacts with subscribed: false |
| Subaccounts | Domain workspaces |
| Inbound domains / routes | Inboxes and email.received webhooks |
How to migrate from Mailchimp Transactional
- 01
Create an API key
Create a key under API keys in the dashboard. Keys start with
et_live_and are scoped to one domain workspace. Store it asEMAILR_API_KEYin your secret manager or environment, never in client-side code. - 02
Add and verify your sending domain
In the dashboard, open Domains and add the domain you send from. A subdomain such as
mail.yourcompany.comkeeps transactional reputation separate from your corporate mailbox.Emailr generates the DNS records: a DKIM TXT record on the
emailr._domainkeyselector, an SPF TXT record, and an MX + TXT pair on thesendsubdomain used as the MAIL FROM / Return-Path. A DMARC record is optional but recommended.Mandrill uses the
mandrill._domainkeyselector; Emailr's selector isemailr._domainkey, so both can be live at once.Emailr's DKIM selector and
sendsubdomain don't collide with other providers' records, so you can add them before cutting over and send from both providers during the migration. A domain can only have one SPF record: if one already exists, merge Emailr'sinclude:into it instead of adding a second record. - 03
Swap the client
Install the SDK and replace
messages.send({ message }). Thetoarray of{ email, type }objects splits intoto,ccandbcc.headers['Reply-To']becomesreply_to_email, andtagsplusmetadatamerge into onetagsobject.Attachments change from
{ type, name, content }to{ contentType, filename, content }; content stays base64.Terminalbashnpm install emailr - 04
Move templates and merge vars
Replace
messages.sendTemplate({ template_name, template_content, message: { global_merge_vars } })withemails.send({ template_id, template_data }). Convert*|FNAME|*merge tags to{{FNAME}}.Emailr templates substitute flat
{{variable}}tokens fromtemplate_data. There are no loops or conditionals, so pre-render lists and optional blocks in your code and pass the result as a variable. - 05
Move the rejection list
Export your rejection denylist and import those addresses as contacts with
subscribed: false. Broadcasts skip them automatically. - 06
Recreate your event webhooks
Mandrill batches events into a
mandrill_eventsform field. emailr_ posts one JSON event per request, so the handler gets simpler.Create a webhook of type
transactional(delivery and engagement events),domain(verification status) orreceiving(inbound email). Each webhook has its own signing secret. Every request carries anX-Emailr-Signatureheader: a hex-encoded HMAC-SHA256 of the raw request body. Verify it before trusting the payload.verify-webhook.tstsimport { createHmac, timingSafeEqual } from 'node:crypto' // rawBody must be the exact request body string, before JSON.parse export function isValidEmailrWebhook(rawBody: string, signature: string | null) { if (!signature) return false const expected = createHmac('sha256', process.env.EMAILR_WEBHOOK_SECRET!) .update(rawBody) .digest('hex') const a = Buffer.from(expected) const b = Buffer.from(signature) return a.length === b.length && timingSafeEqual(a, b) } // const ok = isValidEmailrWebhook(body, req.headers.get('x-emailr-signature')) // const { event, data } = JSON.parse(body) // e.g. event === 'email.bounced'
Webhook event mapping
emailr_ webhooks post one JSON event per request with an event name and a data object. Translate event names in your handler with this table.
| Mailchimp Transactional event | emailr_ event |
|---|---|
| send | email.sent |
| hard_bounce | email.bounced |
| spam | email.complained |
| reject | email.failed |
| open | email.opened |
| click | email.clicked |
Things to watch for
- Per-recipient
merge_varsin a single call aren't supported; send one request per recipient or use Broadcasts with contact properties. - Mandrill's
preserve_recipientsandmerge_languageoptions have no equivalent. - If you used Mailchimp for marketing alongside Mandrill, you can move newsletters to emailr_ Broadcasts too, but it isn't required for the transactional switch.
Frequently asked questions
Do I need a Mailchimp account to use emailr_?
No. emailr_ is standalone. You need an emailr_ API key and a verified sending domain.
How do Mandrill merge tags translate?
Rewrite *|NAME|* as {{NAME}} in the template and pass the values in template_data. Contact properties such as first_name can also be resolved per recipient in broadcasts.
What replaces Mandrill subaccounts?
Domain workspaces. Each workspace has its own domain, templates, contacts and inboxes, and API keys are scoped to one workspace.
Can I schedule sends like send_at?
Yes. Pass scheduled_at with an ISO 8601 timestamp on the send request.
Keep going
Documentation
Related guides
- Migrate from SendGridMove from SendGrid to emailr_: map dynamic templates, categories, suppressions and Event Webhook events, swap the SDK and cut over with zero downtime.
- Migrate from PostmarkMove from Postmark to emailr_: map servers, message streams, templates, tags and metadata, translate webhooks and import suppressions step by step.
- Migrate from MailgunSwitch from Mailgun to emailr_: replace mailgun.js, map tags, variables, templates and webhook events, move suppressions and cut over without downtime.
Ready to leave Mailchimp Transactional?
Create a free account, verify your domain and send your first email through emailr_ in minutes. No credit card required.