How to Self-Host Listmonk: Mailchimp Alternative 2026
TL;DR
Listmonk is a self-hosted newsletter and mailing list manager — 15K GitHub stars, MIT license, written in Go with a React frontend. It handles subscriber management, list segmentation, HTML/plain-text campaigns, transactional emails, and analytics. Requires PostgreSQL. Deploy in 10 minutes with Docker Compose and your own SMTP credentials. Mailchimp charges $13–$350+/month based on subscriber count; Listmonk costs only your server ($6/month) and your SMTP provider.
Key Takeaways
- Listmonk: MIT license, ~15K stars, Go + React, PostgreSQL backend
- Unlimited subscribers: No per-subscriber pricing — you own all the data
- SMTP agnostic: Works with any SMTP provider (SES, Postmark, Mailgun, self-hosted Postfix)
- Transactional email: API for triggered emails (welcome, password reset, receipts)
- Setup time: 10 minutes with Docker Compose
- Cost: Server + SMTP costs only — no per-subscriber fees
Listmonk vs Mailchimp vs Mautic
| Feature | Listmonk (self-hosted) | Mailchimp | Mautic |
|---|---|---|---|
| License | MIT | Proprietary | GPL 3.0 |
| Monthly cost | ~$6/month (server) | $13–$350+/month | Free (self-hosted) |
| Subscriber limits | Unlimited | 500 free, paid tiers | Unlimited |
| Email automation | Basic (campaigns, scheduled) | Advanced | Advanced |
| Transactional email | ✅ API | ✅ | ✅ |
| Open/click tracking | ✅ | ✅ | ✅ |
| Templates | ✅ (HTML editor) | ✅ (drag & drop) | ✅ |
| Bounce handling | ✅ | ✅ | ✅ |
| CRM features | ❌ | ❌ | ✅ |
| Self-hosting complexity | Low (Docker) | N/A | High |
| GitHub Stars | ~15K | — | ~7K |
When Mailchimp/Klaviyo beats Listmonk: Advanced marketing automation, behavioral triggers, and deep e-commerce integrations (Shopify, WooCommerce). Listmonk is purpose-built for newsletters and transactional email — if you need a full marketing automation platform, look at Mautic instead.
Server Requirements
- Minimum: 512MB RAM, 1 vCPU (PostgreSQL + Listmonk)
- Recommended: 1GB RAM, 1 vCPU (comfortable for < 500K subscribers)
- Storage: Small (database + assets — even 1M subscriber records fit in ~500MB)
Part 1: Docker Compose Setup
# docker-compose.yml
version: '3.8'
services:
listmonk:
image: listmonk/listmonk:latest
container_name: listmonk
restart: unless-stopped
ports:
- "9000:9000"
environment:
LISTMONK_app__address: "0.0.0.0:9000"
LISTMONK_db__host: db
LISTMONK_db__port: 5432
LISTMONK_db__user: listmonk
LISTMONK_db__password: "${DB_PASSWORD}"
LISTMONK_db__database: listmonk
LISTMONK_db__ssl_mode: disable
depends_on:
db:
condition: service_healthy
command: [sh, -c, "yes | ./listmonk --install && ./listmonk"]
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: listmonk
POSTGRES_USER: listmonk
POSTGRES_PASSWORD: "${DB_PASSWORD}"
volumes:
- listmonk_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U listmonk"]
interval: 10s
timeout: 5s
retries: 5
volumes:
listmonk_db:
# .env
DB_PASSWORD=strong-database-password-here
docker compose up -d
Visit http://your-server:9000 — Listmonk auto-installs the database schema on first run and presents a setup screen to create your admin account.
Part 2: HTTPS with Caddy
mail.yourdomain.com {
reverse_proxy localhost:9000
}
Or with Nginx:
server {
listen 443 ssl;
server_name mail.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Part 3: Configure SMTP
Go to Settings → SMTP in the Listmonk dashboard. Configure your SMTP provider:
Amazon SES (cheapest at scale)
Host: email-smtp.us-east-1.amazonaws.com
Port: 587
TLS: STARTTLS
Username: [SES SMTP username]
Password: [SES SMTP password]
From: newsletters@yourdomain.com
SES pricing: $0.10/1,000 emails. 1M emails = $100/month.
Postmark (best deliverability)
Host: smtp.postmarkapp.com
Port: 587
TLS: STARTTLS
Username: [your Postmark server API token]
Password: [same API token]
From: newsletters@yourdomain.com
Postmark: $15/month for 10K emails, $150/month for 500K.
Mailgun
Host: smtp.mailgun.org
Port: 587
Username: postmaster@yourdomain.mailgun.org
Password: [Mailgun SMTP password]
Self-Hosted Postfix (free, requires IP warmup)
Host: localhost (or your Postfix container name)
Port: 25
TLS: none (internal)
⚠️ Running your own mail server requires proper DNS setup (SPF, DKIM, DMARC) and IP warmup to avoid spam filters. Use a transactional email provider for production sending.
Part 4: Create Your First List and Campaign
Create a Subscriber List
- Lists → New List
- Name:
Newsletter, Type:Public(subscribers can join via form), orPrivate - Tags:
newsletter,weekly
Import Subscribers
Upload CSV with subscriber data:
email,name,attributes
alice@example.com,Alice Johnson,{"city":"Portland"}
bob@example.com,Bob Smith,{"city":"Seattle"}
Go to Subscribers → Import → Upload CSV → map columns → select lists to add to.
Or import via API:
curl -X POST https://mail.yourdomain.com/api/subscribers \
-u admin:password \
-H "Content-Type: application/json" \
-d '{
"email": "subscriber@example.com",
"name": "John Doe",
"status": "enabled",
"lists": [1]
}'
Create an Email Template
- Templates → New Template
- Paste HTML template — use Listmonk's template variables:
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; }
.header { background: #0066cc; color: white; padding: 20px; }
.content { padding: 30px; }
.footer { color: #666; font-size: 12px; padding: 20px; }
</style>
</head>
<body>
<div class="header">
<h1>{{ .Subscriber.Name }}</h1>
</div>
<div class="content">
{{ template "content" . }}
</div>
<div class="footer">
<p>You're receiving this because you subscribed to our newsletter.</p>
<p><a href="{{ .UnsubscribeURL }}">Unsubscribe</a></p>
</div>
</body>
</html>
Available template variables:
{{ .Subscriber.Email }}— subscriber's email{{ .Subscriber.Name }}— subscriber's name{{ .Subscriber.Attribs }}— custom attributes (JSON map){{ .UnsubscribeURL }}— auto-generated unsubscribe link{{ .Campaign.Name }}— current campaign name
Create and Send a Campaign
- Campaigns → New Campaign
- Name:
March Newsletter 2026 - Subject:
What's new in March 2026 - From email:
newsletters@yourdomain.com - Lists: select your Newsletter list
- Template: choose your template
- Write content in the Content editor (HTML or plain text)
- Send test email → verify in your inbox
- Schedule or send immediately
Part 5: Subscriber Opt-In Forms
Hosted Subscription Form
Every list gets a hosted opt-in page automatically:
https://mail.yourdomain.com/subscription/form
Customize at Settings → Subscription page.
Embed a Subscribe Form on Your Website
<!-- Listmonk embedded subscribe form -->
<form action="https://mail.yourdomain.com/subscription/form" method="POST">
<input type="hidden" name="nonce" value="">
<input type="email" name="email" placeholder="your@email.com" required>
<input type="text" name="name" placeholder="Your name">
<input type="hidden" name="l" value="YOUR_LIST_UUID">
<button type="submit">Subscribe</button>
</form>
Get the list UUID from Lists → click your list → copy UUID from URL.
Double Opt-In
Enable in Settings → Privacy:
- Send confirmation email before activating subscription
- Subscribers confirm by clicking link in email
- Protects against spam signups and improves list quality
Part 6: Transactional Email API
Listmonk's transactional API sends one-off emails (welcome emails, password resets, receipts) to individual subscribers:
# Send a transactional email:
curl -X POST https://mail.yourdomain.com/api/tx \
-u admin:api-token \
-H "Content-Type: application/json" \
-d '{
"subscriber_email": "user@example.com",
"template_id": 2,
"data": {
"order_id": "ORD-12345",
"amount": "$49.00",
"item": "Pro Plan"
},
"content_type": "html"
}'
Transactional template with custom data:
<p>Hi {{ .Subscriber.Name }},</p>
<p>Your order <strong>#{{ .Data.order_id }}</strong> for {{ .Data.amount }} is confirmed.</p>
<p>You subscribed to: {{ .Data.item }}</p>
Part 7: Analytics and Tracking
Listmonk tracks:
- Open rate: Pixel tracking (1x1 image loaded = open)
- Click rate: Links rewritten through tracking proxy
- Bounces: Process bounce emails via webhook from SMTP provider
View Campaign Stats
Campaigns → [campaign name] → Stats:
- Sent, delivered, opened, clicked, bounced, unsubscribed
- Unique vs total opens/clicks
- Timeline chart of engagement over time
Bounce Handling
Configure bounce webhooks in your SMTP provider (SES, Postmark, Mailgun) to POST to:
https://mail.yourdomain.com/webhooks/service/[ses|postmark|mailgun]
Listmonk automatically:
- Marks hard bounces as "blocklisted" (permanent delivery failures)
- Logs soft bounces for manual review
- Prevents re-sending to blocklisted addresses
Part 8: Configuration File (config.toml)
For advanced configuration, mount a config.toml instead of using environment variables:
[app]
address = "0.0.0.0:9000"
admin_username = "admin"
admin_password = "your-password"
[db]
host = "db"
port = 5432
user = "listmonk"
password = "db-password"
database = "listmonk"
ssl_mode = "disable"
max_open = 25
max_idle = 25
max_lifetime = "300s"
[smtp]
[[smtp.servers]]
enabled = true
host = "smtp.postmarkapp.com"
port = 587
auth_protocol = "login"
username = "your-api-token"
password = "your-api-token"
email_headers = []
max_conns = 10
timeout = "5s"
tls_type = "STARTTLS"
tls_skip_verify = false
[privacy]
individual_tracking = true # Track opens per subscriber (vs aggregate only)
unsubscribe_header = true # Add List-Unsubscribe header (improves deliverability)
allow_blocklist = true
allow_export = true
allow_wipe = true
Mount in Docker Compose:
volumes:
- ./config.toml:/listmonk/config.toml:ro
command: ["./listmonk", "--config", "config.toml"]
Maintenance
# Update Listmonk:
docker compose pull
docker compose up -d
# Backup database:
docker exec listmonk-db pg_dump -U listmonk listmonk | \
gzip > listmonk-backup-$(date +%Y%m%d).sql.gz
# Restore:
gunzip -c listmonk-backup-YYYYMMDD.sql.gz | \
docker exec -i listmonk-db psql -U listmonk -d listmonk
# Check logs:
docker compose logs -f listmonk
Cost Comparison
| Service | 10K subscribers | 100K subscribers | 500K subscribers |
|---|---|---|---|
| Mailchimp Essentials | $13/month | $100/month | $350+/month |
| Mailchimp Standard | $20/month | $135/month | $475+/month |
| Klaviyo | $20/month | $150/month | $700+/month |
| ConvertKit | $15/month | $100/month | $400+/month |
| Listmonk + SES | ~$7/month | ~$16/month | ~$56/month |
| Listmonk + Postmark | ~$21/month | ~$81/month | ~$306/month |
Listmonk costs: VPS $6/month + SMTP provider costs. SES: $0.10/1K emails, estimated 2 campaigns/month.
At 100K subscribers, Listmonk + SES saves ~$80–120/month vs Mailchimp — over $1,000/year.
Compare all open source email marketing tools at OSSAlt.com/alternatives/mailchimp.