Case study · Email
How we lowered Mailchimp costs
A client had been sending newsletters through Mailchimp for twelve years. The bill grew with the list. We moved the whole archive to a self-hosted open-source platform, with no loss of active data.
For twelve years the client sent its newsletters through Mailchimp. The platform did its job; the problem was not quality, it was cost and ownership. The archive, the subscribers and the account were attached to a tool that charged more and more as the list grew.
The client asked us to lower the cost without losing the history or the functionality.
From a rented archive to a self-hosted open-source platform: the same features, the client's own history and a predictable cost.
The problem
Not because Mailchimp is bad. It is not, and it is not the point. The point is that an archive of everything a business has ever published to its customers should not be rented. If you cancel the plan, years of newsletters do not become inconvenient — they are gone: the pages, the images, the record of what was said and when.
There is also the cost. A rented list is a bill that grows every time the list grows. The client was paying for the same subscribers again and again, and the history stayed locked to the vendor. A CSV export is not the same thing as ownership.
Together with the client, we set four requirements, not one:
No loss of active data.
The same functionality for sending, archive and subscribers.
A lower, predictable cost.
Maintenance that does not depend on a vendor's ticket queue.
The case study: what we did
The work was not “export, import, done”. It had four parts, and the first was to understand what had to stay the same.
182
campaigns
911
images (785 MB)
14,586
memberships
13,034
active subscribers
We chose Notifuse, a platform with open code under the Business Source License 1.1, and ran it on infrastructure we manage for the client. We pinned our fork to v40.0, so there is no charge per subscriber or per list.
The campaigns
Each campaign became an editable template on the new platform. Its images were copied from Mailchimp's CDN into storage the client controls, and every link was rewritten. Nothing depends on Mailchimp being reachable again.
The dates
A database row created today says today. But a newsletter written in 2014 has to say 2014. Every template, every archive page and every broadcast draft now carries its real send date.
The subscribers
Memberships were imported through the Mailchimp API rather than a CSV, because the API knows each member's status — a spreadsheet only knows what was in the column when someone clicked export. Only subscribed members came across. unsubscribed, cleaned and pending members were left out on purpose.
Importing someone who opted out as though they had not is the one mistake in this process that cannot be taken back.
The archive
The part that mattered most, and the only part no mailer gave the client for free. Mailchimp gives every campaign a public page, forever — the quiet feature you only notice when it is gone. The platform had a blog and a console; neither is that page. So we built it:
/archive/<workspace>/ lists every campaign ever sent, newest first, grouped by year.
/archive/<workspace>/<slug> serves the campaign from the exact template and version that was sent — a pointer, not a copy.
The pages compile through the web channel, without the open-tracking pixel.
A toolbar with Subscribe, Past Issues, RSS and Translate.
The result
The result is not just a different logo in the footer.
No loss of active data. Every campaign, image, date and active subscriber made it across. Inactive statuses were excluded deliberately, not lost.
The same functionality. Sending, editable templates, archive, subscribe, RSS and translation. The difference is who controls the backend, not what the user can do.
A lower, predictable cost. The migration was a one-off engineering cost. The subscription was recurring and grew with the list. Now the cost is the infrastructure we already operate for the client.
Cheaper maintenance. One codebase, one database, one archive. Fixes happen in the fork and, where they affect everyone, are proposed upstream.
What we had to solve
It was not all ready. Three bugs cost us a day each, and each one shows why a migration is not a simple file copy:
A quote in a font stack. Mailchimp escaped the quotes in style attributes as ". The MJML parser decodes an entity before it lexes an attribute, so the quote closed the value early.
Validation in bytes, not characters. A Greek preview of 148 characters is 296 bytes, and the API rejects anything over 255.
A pipe in a title. Splitting SQL output on | is fine until a campaign is called ΧΡΩΜΑ || ΔΙΑΥΓΕΙΑ || ΕΥΚΡΙΝΕΙΑ.
There was also a lesson about status displays: twice the import screen told us something that was not true, because the number was typed in rather than read from the database. A status display that is asserted instead of measured is worse than none.
Why open source
Notifuse is licensed under the Business Source License 1.1: production use is permitted, but a handful of “Licensed Features” require a paid key. We respect that boundary — our fork stays on v40.0 and we declined the 42.1 upgrade partly because it adds a sixth Licensed Feature.
Open source does not mean “zero cost”. It means the cost is predictable and the control does not belong to a vendor.
What is ours, we give back where it is useful to others: the entity-handling fix is a genuine upstream bug, and the campaign archive is a feature we would rather propose than maintain as a private copy forever.
The fork is public, with our build log, the dead ends and the reasons: github.com/agelospanagiotakis/notifuse.
How to leave Mailchimp without losing data
If your newsletter archive lives on a closed platform, the question is not only what it costs per month. It is what will still be yours if the price changes tomorrow, the account closes, or you need a feature the vendor does not have on its roadmap.
The result worth aiming for is not just a smaller invoice. It is lower cost, independence from the vendor and reliable operation that does not depend on someone else's decisions.
Before you leave
Write down what you have. Campaigns, templates, images, lists, custom fields, tags, automations, forms and integrations.
Check subscriber statuses. subscribed is not the same as unsubscribed, cleaned or pending.
Download the assets. Images served from the vendor's CDN are not yours if the account closes tomorrow.
Check what will break. Every integration, form and link has to be tested before the old platform is switched off.
During the migration
Use the API, not just CSV, where one exists. The API carries status and metadata, not only the email column.
Keep the real dates. An archive without correct dates is a pile of pages all claiming they were written today.
Set up domain authentication (SPF, DKIM, DMARC) before the first send.
Test with seed addresses and check bounces before sending to a real audience.
After
Verify that everything arrived. Compare counts, samples and old links. A screen that says “complete” is not proof.
Keep a backup and a rollback plan for the transition period.
Measure the real cost: hosting, maintenance time, sending and support. The cheapest invoice is not always the cheapest system.
Have an archive or a platform that is costing you too much?
See how this could work for your project.
Talk to us