Self-hosting Mautic and Postal on a VPS gives you enterprise-grade marketing automation and email delivery at a fraction of the cost of SaaS tools — but only if you set it up correctly. Get the DNS wrong, skip the warm-up, or under-provision the server, and your campaigns will land in spam no matter how good your copy is.
I deploy this exact stack on client VPS instances every week. This guide is the checklist I follow: sizing, installation, deliverability, and the glue that makes the two tools talk to each other properly.
- Why pair Mautic with Postal instead of using one SaaS
- Choosing and sizing your VPS
- Installing Mautic the right way
- Installing Postal and configuring DNS
- Warming up your IP for deliverability
- Connecting Mautic to Postal
- Monitoring, backups and maintenance
- When to hire someone instead
Why pair Mautic with Postal instead of one SaaS?
Most teams start with Mailchimp, Brevo or HubSpot and hit the same three walls: contact-based pricing that explodes as you grow, data you cannot export cleanly, and sending domains you do not control.
The self-hosted answer splits the problem in two, which is exactly how the big players do it internally:
Mautic handles the brain: contact management, segmentation, drip campaigns, scoring, landing pages, tracking and attribution. It is open source, GPL-licensed, and runs on a PHP/MySQL stack very similar to WordPress.
Postal handles the pipes: it is an open-source mail delivery platform (think “self-hosted SendGrid”) that manages SMTP credentials, multiple sending domains, outbound webhooks, message logs, spam-score inspection and reputation analytics. Unlike plain Postfix, Postal gives you per-domain keys, an HTTP send API and a real dashboard.
Separating the two means your automation logic and your sending reputation scale independently. If an IP gets tired, you move Postal to a new IP — your Mautic segments, campaigns and reports stay untouched.
Need this deployed for you?
I set up Mautic + Postal on your VPS, configure all DNS records, and hand you a working, deliverable system — usually within 3 to 5 days.
Choosing and sizing your VPS
The single most common failure I see is an under-sized server. Mautic cron jobs and Postal workers fight for the same CPU you gave MySQL, and everything crawls.
Minimum sane specs
| Volume (emails/month) | CPU | RAM | Disk | Notes |
|---|---|---|---|---|
| Under 50k | 2 vCPU | 4 GB | 40 GB SSD | Single server, both apps |
| 50k – 250k | 4 vCPU | 8 GB | 80 GB NVMe | Single server, tuned workers |
| 250k – 1M | 6+ vCPU | 16 GB | 160 GB NVMe | Split DB onto a managed instance |
| 1M+ | Multiple | 32 GB+ | NVMe | Separate Mautic and Postal hosts + queue |
Pick the provider for IP reputation, not price
Your sending IP inherits its provider’s reputation. A $4/month box on a range that spammers flooded is a false economy — you will spend weeks fighting blocklists.
- Good for sending: Hetzner, OVH, Contabo (request a clean IP range), Vultr, DigitalOcean — but always check the IP against Spamhaus before you commit.
- Reverse DNS matters: your provider must let you set a PTR record pointing to your mail hostname. If they do not, walk away. Linode/Akamai, Hetzner and OVH all allow it via panel or API.
- Port 25: many providers block outbound port 25 by default. Postal needs it to deliver directly, otherwise you must relay through a provider that allows submission on 587. Ask support to open it before you install anything.
Test any candidate IP first: python3 check_spamhaus.py <ip>, or use MXToolbox blacklist check. One existing listing on CBL or PBL will sink you for weeks.
Installing Mautic the right way
Two viable paths: a traditional LEMP stack, or Docker Compose. For client work I default to Docker when the server is dedicated to automation, and native LEMP when Mautic shares a host with WordPress.
Docker Compose (fastest, cleanest to maintain)
# /opt/mautic/docker-compose.yml
services:
mautic:
image: mautic/mautic:5-apache
links: [mautic-db, mautic-redis]
volumes: ["./app:/var/www/html"]
environment:
MAUTIC_DB_HOST: mautic-db:3306
MAUTIC_DB_NAME: mautic
MAUTIC_DB_USER: mautic
MAUTIC_DB_PASSWORD: ${DB_PASS}
MAUTIC_REDIS_HOST: mautic-redis
MAUTIC_URL: https://mautic.yourdomain.com
MAUTIC_TRUSTED_PROXIES: '["172.16.0.0/12"]'
PHP_MEMORY_LIMIT: 512M
restart: unless-stopped
mautic-db:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
volumes: ["./db:/var/lib/mysql"]
environment:
MYSQL_DATABASE: mautic
MYSQL_USER: mautic
MYSQL_PASSWORD: ${DB_PASS}
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
restart: unless-stopped
mautic-redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes: ["./redis:/data"]
restart: unless-stopped
Put Caddy or nginx in front for TLS, then run the installer once and never touch it again.
Config values people forget
- Set
site_urlcorrectly inapp/config/config.php. A wrong site_url breaks webhook and tracking script URLs silently. - Theme/session handler to Redis (
handler_name: session_handler_redis) — file sessions choke under concurrent cron. - Mailer transport: SMTP, pointing at Postal (covered below). Never “PHP mail” — it bypasses your authenticated domain and kills deliverability.
- Maintenance mode +
mautic:install:douctrine-schemaafter every upgrade, and always take a DB dump first.
The cron jobs that make Mautic actually work
# crontab -u www-data -e
* * * * * php /var/www/html/bin/console mautic:segments:update
* * * * * php /var/www/html/bin/console mautic:campaigns:update
* * * * * php /var/www/html/bin/console mautic:campaigns:trigger
* * * * * php /var/www/html/bin/console mautic:emails:send
* * * * * php /var/www/html/bin/console mautic:import
*/5 * * * * php /var/www/html/bin/console mautic:reports:cache
0 3 * * * php /var/www/html/bin/console mautic:iplookup:download
0 4 * * * php /var/www/html/bin/console mautic:maintenance:cleanup --days=90
If mautic:emails:send or mautic:campaigns:trigger is missing, campaigns simply never fire — and the UI will still look healthy. Verify with grep CRON /var/log/syslog and bin/console list mautic.
Installing Postal and configuring DNS
Postal ships an official installer that provisions Postfix, ClickHouse, RabbitMQ, MongoDB and the Ruby app — but it is picky. It wants a clean Ubuntu 22.04/24.04 LTS box with nothing else on port 80/443.
sudo apt update && sudo apt install -y git
git clone https://github.com/postalserver/postal-docker-compose.git postal
cd postal
docker compose up -d
# then: org creation via CLI
docker compose run --rm postal bin/postal create-organization "Your Company"
Once the web UI is up at https://mail.yourdomain.com, create an Org, an SMTP server (dedicated IP), and a mail domain. Postal then shows you the exact DNS records it expects.
The DNS checklist (non-negotiable)
| Record | Name | Value | Purpose |
|---|---|---|---|
| A | mail.example.com | server IP | Hostname for SMTP/API |
| MX | example.com | mail.example.com (prio 10) | Inbound mail routing |
| SPF (TXT) | example.com | v=spf1 a mx ip4:<IP> include:mail.example.com ~all | Authorises your IP |
| DKIM (TXT) | postal._domainkey | Postal’s public key (shown in UI) | Signature proves integrity |
| DMARC (TXT) | _dmarc.example.com | v=DMARC1; p=none; rua=mailto:dmarc@… | Monitoring policy, start at p=none |
| PTR / rDNS | <IP> | mail.example.com | Set at the VPS provider, not in DNS |
| A | click.example.com | server IP | Tracking/link domain |
| A | inbound.example.com | server IP | Inbound catch-all |
Verify each one with dig +short TXT _dmarc.example.com and Postal’s own “Check DNS” button before sending a single email. Then send a test to a Gmail account and inspect Show original: you want spf=pass, dkim=pass, dmarc=pass and an Authentication-Results header without warnings.
mail.example.com or news.example.com), never your transactional root. If a campaign hurts your reputation, invoices still land in inboxes.Warming up your IP for deliverability
Deliverability is earned in the first 30 days. A fresh IP that jumps to 10,000 sends an hour looks exactly like a spammer, because that is what spammers do.
A schedule that works
| Day | Sends | Notes |
|---|---|---|
| 1–3 | 50–100/day | Only your most engaged contacts; internal Gmail/Outlook/Proton addresses as canaries |
| 4–7 | 200–400/day | Double volume every 2 days, watch bounce rate |
| 2nd week | 1,000–2,500/day | Add a second mailbox provider to your canary list |
| 3rd week | 5,000/day | Start suppressing 90-day non-openers automatically |
| 4th week+ | Scale 2×/week max | Stop immediately if spam complaints exceed 0.1% |
Kill switch thresholds
- Hard bounce > 4% — stop and clean the list. You are listed or your data is stale.
- Spam complaints > 0.1% — Gmail starts throttling silently around 0.3%. Check Postal’s spam-score logs weekly.
- Open rate collapse — a 60%+ drop overnight between campaigns usually means you moved to promotions tab or got greylisted, not that your copy suddenly got worse.
Set up Postmaster Tools (Gmail) and SNDS (Microsoft) from day one. They are the only early-warning system for inbox placement, and they take a few days to show data — register before you need them.
Connecting Mautic to Postal
In Postal, create an SMTP credential on your server. In Mautic: Configuration → Email settings → Mailer transport = Other (SMTP).
host: mail.example.com
port: 587
encryption: TLS
user: <postal smtp credential username>
password: <postal smtp credential secret>
Then two things everyone misses:
- Set a Return-Path. Point it at the Postal catch-all so bounces come back to a mailbox you parse. Mautic’s bounce plugin needs the mailbox.
- Match the tracking domain. If Postal rewrites links through
click.example.com, do not also enable Mautic’s URL tracking — you get double-wrapped links, which triggers spam filters and breaks click reports.
For bounces and complaints, Postal’s webhook can post to a small receiver (n8n is ideal) that updates Mautic contact points automatically. That is what keeps list hygiene out of the manual chore column.
Monitoring, backups and maintenance
Backups that actually restore
#!/usr/bin/env bash
set -euo pipefail
DATE=$(date +%F_%H-%M-%S)
docker compose -f /opt/mautic/docker-compose.yml exec -T mautic-db \
mysqldump -umautic -p"$DB_PASS" --single-transaction mautic | gzip > /backups/mautic-$DATE.sql.gz
tar czf /backups/mautic-app-$DATE.tar.gz -C /opt/mautic app
aws s3 sync /backups s3://your-bucket/mautic/ --exclude '*' --include "*$DATE*"
find /backups -mtime +14 -delete
Test a restore quarterly. A backup nobody has restored is a rumour.
Monitoring essentials
- Uptime Kuma — HTTP checks on Mautic UI, Postal UI, plus a TCP check on port 25 and 587 from an external host. Internal checks tell you nothing about mail flow.
- Mail queue depth — Postal exposes queue metrics; alert when pending messages exceed ~1000 or oldest exceeds 15 minutes.
- Cron heartbeat — use Healthchecks.io or a Kuma push monitor on
mautic:campaigns:trigger. Silent cron death is the #1 “why did my campaign not send” ticket I get asked to fix. - Blacklist watch — weekly Spamhaus check on the sending IP, alert on listing.
When to hire someone instead
Self-hosting Mautic + Postal is genuinely worth it if you send more than ~25k emails a month, need to own your data, or are tired of contact-based SaaS pricing.
It is not worth it if you cannot spare a few hours a month on maintenance, or if your first campaign failing to arrive would cost you a customer contract. In that case, get it deployed professionally, then learn to operate it.
Here is the honest split:
- Do it yourself if you are comfortable with Docker, DNS, and reading postfix logs when something breaks.
- Hire help for the deliverability layer specifically — DNS architecture, IP selection, warm-up plan, and bounce automation. Those are the parts where a cheap mistake costs you months of reputation.
Want me to build it end to end?
Full deployment: Mautic + Postal on your VPS, complete DNS setup, deliverability configuration, bounce automation, monitoring and a handover doc — plus a n8n workflow to keep the stack in sync.
Book the Mautic + Postal setup →
Prefer the automation side? target=”_blank” rel=”noopener”>See the OpenClaw AI agent gig →
Frequently asked questions
Can I run Mautic, Postal and WordPress on one VPS?
Yes, up to roughly 50k emails/month on 8 GB RAM, provided you isolate them in separate Docker networks and give MySQL its own memory allocation. Beyond that, split Postal onto its own box so mail delivery is never starved by a WordPress traffic spike.
Is Postal better than just using Postfix?
For marketing sending, yes. Postfix alone gives you no per-domain keys, no sending dashboard, no message logs, no spam-score inspection and no HTTP API. Postal runs Postfix underneath but wraps it with everything a sending operation needs.
What happens if my IP gets blacklisted?
You request delisting (Spamhaus/CBL forms take 24–72 hours) and you keep sending — because Postal lets you add a second mail server on a new IP and shift domains over. That isolation is the entire reason for running a separate MTA.
Mautic 4 or Mautic 5?
Mautic 5 if you are starting fresh: PHP 8.2+ support, redesigned UI, better queue handling. Mautic 4 only if you depend on a plugin that has not been updated. Never upgrade in place without a tested DB dump.
How long does a professional setup take?
Installation is a day. DNS propagation and IP warm-up mean you are sending at real volume in roughly 2–3 weeks. Anyone promising instant deliverability on a fresh IP is guessing.
