Listmonk on a VPS: Self-Hosted Email Newsletter Platform for High-Volume Campaigns

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

  1. Admin → Lists → New List
  2. Name: Weekly Newsletter, Type: Public (subscribers can join) or Private
  3. Opt-in: Single (immediate) or Double (confirmation email required — recommended for deliverability)
  4. 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

  1. Admin → Campaigns → New Campaign
  2. Name: June Newsletter, Subject: What's new in June
  3. From: Newsletter <newsletter@yourdomain.com>
  4. Lists: select which lists receive this campaign
  5. Template: choose or create HTML template
  6. Content: write email in the HTML editor or import HTML
  7. 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.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!