Migrate from Mailchimp to Listmonk
Email marketing / newsletters · full migration guide
Mailchimp → Listmonk is a CSV-bridged migration with a deliverability twist. The data move itself is short: export the audience as CSV, drop into Listmonk's import. The actual work is upstream — your Mailchimp account is sending from Mailchimp's IPs with their reputation; your Listmonk install is sending from your VPS or, more commonly, an SMTP relay (Amazon SES, Postmark, Mailgun) whose reputation you must establish or piggyback on. Plan for a deliverability ramp: send to your most engaged segment first, watch bounce + complaint rates for a week, then expand.
Prerequisites
- Mailchimp admin access to the audiences you want to export.
- A self-hosted Listmonk instance — see /mailchimp/ for the 10-minute docker-compose recipe and ~$5/mo VPS sizing.
- An upstream SMTP sender: Amazon SES at ~$0.10/1k emails (cheapest, requires DKIM + production-access form), Postmark, or your own MTA. Listmonk does not send directly — it relays.
- DKIM, SPF, and DMARC records configured on your sending domain — same domain Mailchimp was using is fine, but the keys are the relay's (SES) not Mailchimp's.
- A test list (your team's emails) for sending the first 5-10 campaigns to verify deliverability before pointing the real list at the new infra.
Step 1 — Export from Mailchimp
-
Export the Mailchimp audience CSV
In Mailchimp: Audience → All contacts → Export Audience. Choose all subscribers (or filter to subscribed-only for a clean import). Mailchimp processes the export in the background; you get an email with a download link in 5-15 minutes.
-
Inspect and trim the CSV
Open the CSV. Mailchimp emits 30+ columns including merge fields (FNAME, LNAME, MMERGE3, etc), tags, and engagement scores. The columns Listmonk needs: Email Address, First Name, Last Name, Subscriber Status. Tags can be preserved as a comma-separated `tags` column.
-
Export campaign templates (optional)
Mailchimp does not offer a clean template export. Open each campaign you want to preserve, click View Email → Source Code, and save the rendered HTML. Mailchimp merge tags (`*|FNAME|*`) need to be manually rewritten as Listmonk's Go template tags (`{{ .Subscriber.Attribs.first_name }}`).
-
Note your Mailchimp segments
Listmonk's `lists` are a bit looser than Mailchimp's segments. Document each segment's filter criteria (signup source, tag, engagement) — you'll re-implement these as Listmonk lists or as queries on the `subscribers` table.
Step 2 — Import into Listmonk
-
Create lists in Listmonk
In Listmonk admin → Lists → New list. Create one list per Mailchimp audience (or per segment if you want finer control). Set the list type (public/private) and the opt-in mode (single/double — single is what Mailchimp does by default; double is the GDPR-friendly choice for new EU subscribers).
-
Import the CSV
Listmonk admin → Subscribers → Import. Upload the trimmed CSV. Map columns: Email Address → email, First Name → name (Listmonk has only a single name field — concatenate first + last in the CSV before import). Choose the destination list. Choose 'subscribed' as the default status. Click Import. For 50k subscribers expect 1-2 minutes.
-
Configure the SMTP sender
Listmonk admin → Settings → SMTP. Enter your relay's host (e.g. `email-smtp.us-east-1.amazonaws.com` for SES), port 587, STARTTLS, your SES SMTP credentials. Set the From address to a verified domain. Test send to your own email — verify it arrives without going to spam.
-
Recreate templates
Listmonk → Campaigns → Templates. Paste the Mailchimp HTML, then rewrite merge tags. Mailchimp's `*|FNAME|*` becomes `{{ .Subscriber.FirstName }}` (or `{{ .Subscriber.Attribs.first_name }}` if stored in the JSONB attribs). Test with a single-recipient send to a list of one before sending broadly.
-
Send a warmup campaign to your team list first
Send the first real campaign to your internal/team list (10-50 addresses). Check engagement: opens > 50%, no bounces, no spam folder. Then send to your most engaged segment (last 30 days openers). Then expand to the full list over a few campaigns. SES + a cold IP can flag if you go from 0 to 50k in one day.
Field / concept mapping
| Mailchimp | Listmonk | Notes |
|---|---|---|
| Mailchimp Audience | Listmonk List | 1:1. Each audience becomes a list. |
| Mailchimp Subscriber | Listmonk Subscriber | Email is the unique key. Status maps: subscribed → enabled, unsubscribed → disabled, cleaned (bounced) → blocklisted. |
| Mailchimp Tags | Listmonk Subscriber attribs (JSONB) | Tags become a `tags` array in the JSONB attribs column. Filter campaigns by `attribs->>'tags' LIKE '%vip%'`. |
| Mailchimp Merge field (FNAME, etc) | Listmonk attribs key | `FNAME` becomes `attribs.first_name`. Migrate via the CSV import column → attribs mapping. |
| Mailchimp Segment | Listmonk segment via SQL | Listmonk lets you write a SQL WHERE clause as a campaign segment. Recreate Mailchimp segments as queries: `attribs->>'signup_source' = 'webinar' AND last_open > now() - interval '30 days'`. |
| Mailchimp Campaign | Listmonk Campaign | Recreated from scratch. Subject, from name, content (HTML), template, list selection. Listmonk does not import past campaign history — it's a forward-only system. |
| Mailchimp Automation | Listmonk Bounce / no native automation | Drip campaigns / journeys are not a Listmonk feature (they are a Mautic feature). For multi-step automation, pair Listmonk with n8n: trigger a Listmonk campaign send via API on a schedule. |
| Mailchimp Landing page | Not migrated | Listmonk does not have landing pages. Host the form on your own site or use a separate tool (e.g. Formbricks, simple HTML form posting to Listmonk's `/api/public/subscription`). |
| Mailchimp Signup form | Listmonk public signup form | Listmonk exposes `/api/public/subscription` for new signups. Build a small HTML form on your site that POSTs to it. |
Downtime estimate
Zero downtime if you cut over campaign-by-campaign. Two weeks of overlap is normal: Mailchimp keeps sending the existing automations while you spin up Listmonk and validate the first few sends. Last campaign on Mailchimp can be a 'we've moved senders' announcement to set expectations. Hard cutover would be ~1 hour of write-freeze on the audience for the import, but the dual-write path is essentially free.
Common gotchas
- Cold IP reputation is the #1 reason post-migration sends go to spam. Use SES's pre-warmed shared IPs (default) rather than a dedicated IP unless you have proven volume.
- Mailchimp's compliance footers (physical address, unsubscribe link) are required in many jurisdictions — Listmonk can auto-inject the unsubscribe link via `{{ UnsubscribeURL }}`, but you must paste your physical address into Settings → General.
- Mailchimp's default double-opt-in is preserved in their export status field; Listmonk's import treats everything as already opted-in. For EU recipients, consider re-confirming opt-in on the Listmonk side to stay GDPR-clean.
- Mailchimp Automations (welcome series, abandoned cart) do not have a Listmonk equivalent — those are a Mautic feature. Decide upfront whether you're moving newsletters only (Listmonk) or also lifecycle automation (also Mautic / pair both).
- Past campaign performance (open rates, click rates) does not transfer. If you need historical reporting, archive Mailchimp's campaign reports (PDF or screenshot) before cancelling the account.
Rollback plan
Mailchimp does not delete on export — your audience and campaigns are intact for as long as the account stays paid. The cleanest rollback is to just keep the Mailchimp account active for 30-60 days post-cutover; if Listmonk's deliverability proves worse than Mailchimp's, point campaigns back. The CSV export can also be re-imported into Mailchimp later (Audience → Import) to restore any newly-acquired subscribers Listmonk gained during the trial period.
Looking for setup time, monthly cost, and other alternatives? See Self-host Mailchimp.