Listmonk on a VPS: Self-Hosted Email Newsletter Platform for High-Volume Campaigns
Listmonk is a high-performance, open-source newsletter and mailing list manager written in Go. It handles millions of subscribers, transactional emails, multi-list segmentation, template management, bounce tracking, and analytics — all from a single Go binary with a PostgreSQL backend. Mailchimp charges $500+/month for 500,000 subscribers. Listmonk plus Amazon SES at the same volume costs $50/month total.
Why Listmonk Over Mautic
- Listmonk: Simpler, faster, focused on newsletters and transactional email. Single binary, lightweight (50 MB RAM). No automation flows.
- Mautic: Full marketing automation — drip campaigns, lead scoring, CRM integration, landing pages. Heavier (512 MB+ RAM).
- Choose Listmonk: High-volume newsletter sending, simple segmentation, transactional email API, no marketing automation needed.
- Choose Mautic: Complex automation workflows, lead nurturing sequences, CRM integration.
Step 1: Docker Compose Setup
<code">mkdir -p /opt/listmonk && cd /opt/listmonk nano docker-compose.yml
<code">version: '3.8'
services:
listmonk:
image: listmonk/listmonk:latest
container_name: listmonk
restart: always
ports:
- "127.0.0.1:9000:9000"
environment:
LISTMONK_app__address: "0.0.0.0:9000"
LISTMONK_db__host: listmonk-db
LISTMONK_db__port: 5432
LISTMONK_db__user: listmonk
LISTMONK_db__password: ${POSTGRES_PASSWORD}
LISTMONK_db__database: listmonk
LISTMONK_db__ssl_mode: disable
volumes:
- listmonk_uploads:/listmonk/uploads
depends_on:
listmonk-db:
condition: service_healthy
command: [sh, -c, "yes | ./listmonk --install && ./listmonk"]
listmonk-db:
image: postgres:16-alpine
container_name: listmonk-db
restart: always
environment:
POSTGRES_DB: listmonk
POSTGRES_USER: listmonk
POSTGRES_PASSWORD: ${POSTGRES_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:
listmonk_uploads:
<code">echo "POSTGRES_PASSWORD=StrongListmonkPassword!" > .env chmod 600 .env docker compose up -d docker compose logs -f listmonk
Step 2: Nginx Reverse Proxy
<code">sudo nano /etc/nginx/sites-available/listmonk
<code">server {
listen 80;
server_name mail.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
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;
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_http_version 1.1;
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;
}
}
<code">sudo ln -s /etc/nginx/sites-available/listmonk /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx sudo certbot --nginx -d mail.yourdomain.com
Step 3: Create Admin Account
<code"># Create admin user docker compose exec listmonk ./listmonk --idempotent --install docker compose exec listmonk ./listmonk admin --reset # Or visit https://mail.yourdomain.com/admin # Default credentials created during first run are shown in logs
Step 4: Configure SMTP (Amazon SES)
In Listmonk Admin → Settings → SMTP:
<code"># Amazon SES SMTP (best for high volume, $0.10/1000 emails) Host: email-smtp.us-east-1.amazonaws.com Port: 587 Username: YOUR_SES_SMTP_USERNAME Password: YOUR_SES_SMTP_PASSWORD TLS: STARTTLS Email batch size: 50 # Emails per SMTP connection Send timeout: 5 seconds # Alternative: Postmark (better deliverability for transactional) Host: smtp.postmarkapp.com Port: 587 Username: YOUR_POSTMARK_API_TOKEN Password: YOUR_POSTMARK_API_TOKEN # Alternative: Mailgun Host: smtp.mailgun.org Port: 587 Username: postmaster@mg.yourdomain.com Password: your_mailgun_password
Step 5: Set Up Mailing Lists
- Admin → Lists → New List
- Name:
Weekly Newsletter, Type: Public (subscribers can join) or Private - Opt-in: Single (immediate) or Double (confirmation email required — recommended for deliverability)
- Create multiple lists for segmentation: Newsletter, Product Updates, Announcements
Step 6: Import Subscribers
<code"># CSV import format:
# email,name,attributes
# user@example.com,John Doe,"{""plan"": ""pro"", ""country"": ""US""}"
# Import via API:
curl -X POST https://mail.yourdomain.com/api/subscribers/import \
-u admin:password \
-F "file=@subscribers.csv" \
-F "list_ids=1,2" \
-F "mode=subscribe"
# Check import status:
curl https://mail.yourdomain.com/api/import/subscribers \
-u admin:password
Step 7: Create and Send a Campaign
- Admin → Campaigns → New Campaign
- Name:
June Newsletter, Subject:What's new in June - From:
Newsletter <newsletter@yourdomain.com> - Lists: select which lists receive this campaign
- Template: choose or create HTML template
- Content: write email in the HTML editor or import HTML
- Start → Campaign sends at configured batch rate
Step 8: Transactional Emails via API
<code"># Send transactional email (e.g., password reset, order confirmation)
curl -X POST https://mail.yourdomain.com/api/tx \
-u admin:password \
-H "Content-Type: application/json" \
-d '{
"subscriber_email": "user@example.com",
"template_id": 2,
"data": {
"username": "john",
"reset_link": "https://yourdomain.com/reset?token=abc123"
}
}'
Step 9: Bounce and Unsubscribe Handling
<code"># Configure bounce webhook in SES:
# SES → Identities → your domain → Notifications
# Bounce topic → create SNS topic → create HTTPS subscription to:
# https://mail.yourdomain.com/webhooks/service/ses
# Listmonk processes bounces automatically:
# Hard bounce → subscriber blacklisted
# Soft bounce (3×) → subscriber flagged
# Unsubscribes use Listmonk's built-in one-click unsubscribe link
# Add to template: {{ UnsubscribeURL }}
Getting Started
Listmonk runs on 50–100 MB RAM — it fits on any Ubuntu VPS at VPS.DO alongside other services. The Go binary is memory-efficient even when sending millions of emails. Amazon SES at $0.10/1,000 emails means 500,000 subscribers receiving monthly newsletters costs $50 in SES fees — compared to $500+/month on Mailchimp. Configure SES production access (request sandbox removal) before sending to external subscribers.
Conclusion
Listmonk plus Amazon SES is the most cost-effective high-volume email newsletter stack available. The Go binary handles millions of subscribers and high sending rates with minimal RAM, while SES provides enterprise-grade deliverability at commodity pricing. For publishers, SaaS products, and media companies sending regular newsletters to large lists, self-hosted Listmonk eliminates the per-subscriber fee that makes Mailchimp, ConvertKit, and similar services expensive at scale.