Escaping SaaS Lock-In: Our Self-Hosted Mautic 7.1.2 + Chatwoot Stack on Hetzner
Australian SaaS spend hit $18.4 billion in FY2024 according to the ACS Digital Pulse report, growing 14% year on year — with the average mid-market business now running 112 SaaS subscriptions (ACS Australia’s Digital Pulse 2024). At some point, most operators do the maths and realise a chunk of that bill is being paid to marketing-automation and support-chat tools that could run on a $40/month Hetzner box if anyone was willing to actually run it.
We were willing. This is the runbook — the real commits, the real infrastructure, and the war stories from the migration.
What we replaced
| SaaS tool | Function | Replaced by |
|---|---|---|
| HubSpot Marketing Starter | contact list, email campaigns, workflows | Mautic 7.1.2 self-hosted |
| Intercom Starter | live chat, help widget, ticket routing | Chatwoot self-hosted |
| (bounce handling) | SES bounce → suppression | Custom SES → SNS → SQS → Mautic DNC worker |
Hosting: a single Hetzner Cloud box for the app tier, Hetzner-managed MySQL for the database, S3 for asset storage, and AWS SES as the sending MTA. Monthly cost sits at ~AUD $60-90/month all-in depending on send volume, versus the four-figure equivalent SaaS stack we were on.
The fresh Mautic 7.1.2 install — 24 June 2026
The commit that shipped the installation runbook:
2026-06-24 —
infra(mautic): fresh install 7.1.2 on Hetzner + DMARC archive + runbook— +338 lines (Email-Address-Extractorrepo)
Three things landed together in that push:
- Mautic 7.1.2 on Hetzner — the fresh install, not a migration from an earlier version. We deliberately chose a clean 7.x base after seeing the migration pain (see below) that teams still on 5.x/6.x hit when they leap versions.
- DMARC archive — inbound DMARC reports go to a designated mailbox, get archived, and get parsed into a spreadsheet you can actually read. Nobody looks at DMARC reports unless someone builds this — and nobody who cares about deliverability can afford not to.
- Runbook — the actual document. Server sizing, PHP-FPM tuning, cron schedule, backup cadence, restore procedure. When your marketing team goes to send Christmas campaigns and something’s off, the runbook is what turns a Tuesday incident into a Tuesday non-event.
The custom middleware: chatwoot-mautic-webhook
Most SaaS-escape stories fall down at integration. HubSpot’s value isn’t the email sending — it’s that HubSpot knows when a contact opens a chat and can trigger a workflow off it. Self-host Mautic + Chatwoot and you get two separate systems that don’t talk to each other. Unless you write the middleware.
We did. The chatwoot-mautic-webhook repository is the piece nobody wants to talk about: a small custom service that receives Chatwoot’s conversation events, matches them to Mautic contacts by email, and updates Mautic tags / notes / stage without human involvement.
That’s the difference between “we self-host our tools” (technically true, operationally broken) and “we’ve escaped SaaS lock-in” (contact activity from every surface lands in the same customer record).
The Mautic 5→7 Docker migration — the war story
Before the clean 7.1.2 install, we did the harder version — migrating a live 5.x instance forward. That work landed as a series in early July 2026:
| Commit | Date | Lines added |
|---|---|---|
feat(blog): Mautic 5→7 Docker migration war-story post | 2026-07-09 | +217 |
feat(blog): Posts C and D of Mautic migration series (draft: true) | 2026-07-09 | +294 |
The failure modes worth naming (all reproduced during our upgrade):
- MySQL character set mismatch — 5.x defaulted to
utf8, 7.x expectsutf8mb4. Emoji and some non-Latin characters in your contact list will corrupt if you don’t convert the tables before upgrading. - Timezone drift — Mautic 7.x is stricter about timezone-aware timestamps. Campaigns scheduled in 5.x on a server with an ambiguous TZ setting can fire hours off after the upgrade.
- Orphan campaign tables — 5.x left behind rows in
campaign_eventsreferencing deleted campaigns. 7.x FK constraints reject the upgrade until you clean the orphans. - Docker-Compose volume ordering — the database volume must survive the container recreation. If your
docker-compose downincludes-vin a script somewhere, you find that out at the worst possible moment.
Every one of the above is fixable in ~30 minutes if you know it’s coming. It’s a wasted afternoon if you don’t. That’s what a runbook is for.
The deliverability layer — SES → SNS → SQS → Mautic DNC
Marketing automation without deliverability is a $60/month box that sends mail into Gmail spam. The July 2026 commit that closed that loop:
2026-07-03 —
feat(mautic): SES bounce → SNS → SQS → Mautic DNC worker (Phase 2)— +325 lines (Email-Address-Extractorrepo)
Read left-to-right: AWS SES emits a bounce event; SNS fans it out; SQS queues it; a small worker reads the queue and marks the email as DNC (Do Not Contact) inside Mautic. Bounced addresses stop being sent to within seconds, not “next campaign.”
This is what SaaS marketing platforms give you for free. Self-host and you either build this or watch your sender reputation degrade until Gmail starts silently dropping your mail.
The cost delta
For our own operation, the specific numbers:
| Line item | SaaS (before) | Self-hosted (now) |
|---|---|---|
| Marketing-automation platform | ~AUD $650/mo (HubSpot Marketing Starter) | AUD $0 (open source) |
| Live-chat platform | ~AUD $110/mo (Intercom Starter) | AUD $0 (open source) |
| Hetzner Cloud (app + DB) | — | ~AUD $45/mo |
| AWS SES (10K sends/mo) | — | ~AUD $12/mo |
| S3 assets + backup | — | ~AUD $8/mo |
| Total | ~$760/mo | ~$65/mo |
| Annual saving | ~$8,300/yr |
Above 15K contacts and 50K monthly sends, the savings scale — because the SaaS bill scales with those numbers and the Hetzner + SES bill barely moves.
When self-hosting is the wrong answer
To be fair to the SaaS tools:
- Small team, no ops appetite. If nobody on your team can safely restart a Docker service at 10pm, self-hosting anything with campaign timing dependencies is a liability. The $760/mo SaaS bill is buying you not being on call.
- Compliance requirements you don’t want to inherit. GDPR / Australian Privacy Principles / HIPAA-equivalent duties are the SaaS vendor’s problem when you’re on their platform, and yours the moment you self-host.
- Sub-1,000 contacts. The bill is too small for the migration effort to pay back inside 12 months.
Cloud Geeks managed IT — what this maps to
If you’d rather have someone else run the box, Cloud Geeks’ managed IT engagements cover this exact category of work. Our service tiers and inclusions are on the main site — see the Managed IT Services page for what’s included at each level. The self-hosting playbook above is what we install for clients who prefer to own the stack rather than rent it.
Frequently asked questions
Is self-hosted Mautic actually stable enough for production marketing?
Yes, on 7.x. The 5.x line had rough edges; 7.1.2 is production-grade for the SMB / mid-market use case we deploy for. The critical prerequisites are a runbook (so an ops-competent person can restore in an hour), a working backup, and the DMARC + SES bounce handling closed. Without those, self-hosted anything degrades.
Why Chatwoot instead of continuing with Intercom?
Intercom’s per-seat pricing scales badly for the “let a couple more team members answer chats” case. Chatwoot delivers the core inbox + agent routing + widget without seat pricing. The trade-off is you lose Intercom’s product-marketing integrations — worth it if the chat is customer-support-first, expensive if it’s product-marketing-first.
How hard is the Mautic 5 to 7 migration in Docker?
Manageable in an afternoon if you know the four failure modes to avoid: MySQL charset (utf8→utf8mb4), timezone stripness, orphan campaign_events rows, and Docker-Compose volume ordering. It’s a wasted weekend and a corrupt contact database if you don’t. Our July 2026 Mautic migration series documents the specific fixes for each.
Do you set this up for clients or is it DIY-only?
Both. The runbook above lets a competent ops-capable team self-serve. Cloud Geeks’ managed IT engagement covers the full install-and-operate path (fresh Mautic 7.1.2 on Hetzner, Chatwoot on the same or a separate box, chatwoot-mautic-webhook middleware, SES bounce worker, DMARC archive, monthly runbook review) for clients who prefer to own the stack without operating it themselves.
How does this affect email deliverability compared to HubSpot?
Warmed correctly, self-hosted Mautic → SES beats HubSpot’s shared pool for engaged-list sending because you’re not competing with 40,000 other senders on the same IPs. It underperforms HubSpot for cold-list blasts because the shared pool has more reputation-diversity to hide behind. The right answer for most Australian SMBs is engaged-list sending, which is where the self-hosted stack wins.
Related
- The gate stack that runs on top of Mautic — Ashganda on adversarial verification and fail-closed gates — same pattern applied to content publishing rather than email sending.
- Companion posts — the Mautic 5→7 Docker migration war story walks through each of the four failure modes with the specific fix scripts.
- Book a scoping call — Cloud Geeks Managed IT for the “install this stack for us and run it” engagement.