Skip to main content

How to Self-Host Listmonk: Mailchimp Alternative 2026

·OSSAlt Team
listmonkmailchimpemail-marketingself-hostingdockernewsletter2026

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

FeatureListmonk (self-hosted)MailchimpMautic
LicenseMITProprietaryGPL 3.0
Monthly cost~$6/month (server)$13–$350+/monthFree (self-hosted)
Subscriber limitsUnlimited500 free, paid tiersUnlimited
Email automationBasic (campaigns, scheduled)AdvancedAdvanced
Transactional email✅ API
Open/click tracking
Templates✅ (HTML editor)✅ (drag & drop)
Bounce handling
CRM features
Self-hosting complexityLow (Docker)N/AHigh
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

  1. Lists → New List
  2. Name: Newsletter, Type: Public (subscribers can join via form), or Private
  3. 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

  1. Templates → New Template
  2. 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

  1. Campaigns → New Campaign
  2. Name: March Newsletter 2026
  3. Subject: What's new in March 2026
  4. From email: newsletters@yourdomain.com
  5. Lists: select your Newsletter list
  6. Template: choose your template
  7. Write content in the Content editor (HTML or plain text)
  8. Send test email → verify in your inbox
  9. 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

Service10K subscribers100K subscribers500K 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.

Comments