Skip to main content

How to Self-Host Listmonk: Email Newsletter and Mailing List Server 2026

·OSSAlt Team
listmonknewsletteremailmarketingself-hostingdocker2026

TL;DR

Listmonk (AGPL 3.0, ~14K GitHub stars, Go + React) is a self-hosted email newsletter and mailing list manager — a free Mailchimp replacement. It manages subscribers, sends campaigns, tracks opens and clicks, handles bounces, and segments audiences. Mailchimp charges $13/month for 500 subscribers; Listmonk costs only the price of your transactional email provider (SES, Postmark, Mailgun). Single Go binary + PostgreSQL — handles millions of subscribers.

Key Takeaways

  • Listmonk: AGPL 3.0, ~14K stars, Go — Mailchimp alternative, high performance
  • Unlimited subscribers: No per-subscriber pricing — pay only for email delivery
  • Templates: HTML + plain text email templates with Go templating
  • Segmentation: Lists + dynamic subscriber segments with custom attributes
  • Bounce handling: Automatic bounce and complaint processing (SES/Postmark webhooks)
  • API: Full REST API for programmatic subscriber management

Listmonk vs Mailchimp vs Ghost Newsletters

FeatureListmonkMailchimpGhost
LicenseAGPL 3.0ProprietaryMIT
Cost (500 subs)Free + SMTP$13/moFree hosting
Cost (10K subs)Free + SMTP$100/mo~$25/mo cloud
TransactionalVia SMTP providerNoNo
Subscriber attributesCustom fieldsMerge tagsNo
Dynamic segmentsYesYesNo
Open/click trackingYesYesYes
Double opt-inYesYesYes
Bounce handlingYesYesYes
Multi-listYesYesVia publications
APIFull RESTRESTContent API
Self-hostedYesNoYes

Part 1: Docker Setup

# docker-compose.yml
services:
  listmonk:
    image: listmonk/listmonk:latest
    container_name: listmonk
    restart: unless-stopped
    ports:
      - "9000:9000"
    volumes:
      - ./config.toml:/listmonk/config.toml:ro
      - listmonk_uploads:/listmonk/uploads
    depends_on:
      db:
        condition: service_healthy
    command: ["./listmonk", "--config", "/listmonk/config.toml"]

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: listmonk
      POSTGRES_USER: listmonk
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U listmonk"]
      interval: 10s

volumes:
  listmonk_uploads:
  postgres_data:
# config.toml
[app]
address = "0.0.0.0:9000"

[db]
host = "db"
port = 5432
user = "listmonk"
password = "YOUR_POSTGRES_PASSWORD"
database = "listmonk"
ssl_mode = "disable"
max_open = 25
max_idle = 25
max_lifetime = "300s"
# Initialize the database:
docker compose run --rm listmonk ./listmonk --install

# Start:
docker compose up -d

Visit http://your-server:9000 → log in (default: admin / listmonk — change immediately).


Part 2: HTTPS with Caddy

newsletter.yourdomain.com {
    reverse_proxy localhost:9000
}

Part 3: Configure Email (SMTP)

Settings → SMTP

Amazon SES

SMTP Host: email-smtp.us-east-1.amazonaws.com
SMTP Port: 587
Auth: STARTTLS
Username: your-ses-smtp-username
Password: your-ses-smtp-password
From: "Your Newsletter" <newsletter@yourdomain.com>

Postmark

SMTP Host: smtp.postmarkapp.com
SMTP Port: 587
Username: your-postmark-server-api-token
Password: your-postmark-server-api-token
From: newsletter@yourdomain.com

Self-Hosted (Maddy/Postfix)

SMTP Host: mailserver.yourdomain.com
SMTP Port: 587
From: newsletter@yourdomain.com

Test the configuration with Send Test Email.


Part 4: Subscriber Management

Create a Mailing List

  1. Lists → + New List
  2. Name: "Weekly Newsletter"
  3. Type: Public (users can subscribe via form) or Private (admin-only)
  4. Opt-in type: Double opt-in (recommended) or Single opt-in

Import Subscribers

email,name
alice@example.com,Alice Smith
bob@example.com,Bob Jones
  1. Subscribers → Import → Upload CSV
  2. Map CSV columns to subscriber fields
  3. Assign to list(s)
  4. Listmonk imports and (optionally) sends double opt-in confirmation

Custom Subscriber Attributes

Add custom data per subscriber:

{
  "plan": "premium",
  "signup_date": "2026-01-15",
  "country": "US"
}

Use in email templates: {{ .Subscriber.Attribs.plan }}

Subscription Form

Listmonk generates an embeddable HTML form:

<form action="https://newsletter.yourdomain.com/subscription/form" method="post">
  <input type="hidden" name="nonce" />
  <input type="email" name="email" placeholder="Your email" required />
  <input type="text" name="name" placeholder="Your name" />
  <input type="hidden" name="l" value="LIST_UUID" />
  <button type="submit">Subscribe</button>
</form>

<script src="https://newsletter.yourdomain.com/public/static/listmonk.js"></script>

Part 5: Creating Campaigns

Draft a Campaign

  1. Campaigns → + New Campaign
  2. Name: "March Newsletter"
  3. Subject: "Your March Update"
  4. From name/email
  5. Select lists: "Weekly Newsletter"
  6. Template: Choose or create

Email Templates

Listmonk uses Go templates:

<!-- template.html -->
<!DOCTYPE html>
<html>
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
  <h1>{{ .Campaign.Name }}</h1>
  
  <p>Hi {{ .Subscriber.FirstName }},</p>
  
  {{ template "content" . }}
  
  <hr>
  <p style="font-size: 12px; color: #666;">
    You're receiving this because you subscribed to {{ .Lists.String }}.
    <a href="{{ .UnsubscribeURL }}">Unsubscribe</a>
  </p>
</body>
</html>

Campaign Content (Markdown or HTML)

# March Update

This month we shipped:

- **Feature A**: Description of feature A
- **Feature B**: Description of feature B

[Read the full changelog →](https://yourdomain.com/changelog)

Thanks for reading,
The Team

Schedule or Send

  • Send Now: Delivers immediately
  • Schedule: Pick a future date/time
  • Batch size: Controls send rate (e.g., 1000/hour to avoid spam filters)

Part 6: Analytics

After sending, Campaigns → [Campaign] → Stats:

  • Sent: Total emails delivered
  • Opens: % opened (requires tracking pixel)
  • Clicks: % clicked any link
  • Bounces: Hard/soft bounces (requires bounce webhook)
  • Unsubscribes: Unsubscribe rate

Part 7: Bounce Handling (SES)

Automatically remove hard-bounced emails:

  1. In AWS SES: Email → Notifications → Bounce topic → SNS topic
  2. SNS → Subscribe → HTTPS endpoint: https://newsletter.yourdomain.com/webhooks/service/ses
  3. In Listmonk: Settings → Bounce → Enable SES bounces

Listmonk automatically:

  • Disables hard-bounced subscribers
  • Marks complaint emails as unsubscribed

Part 8: REST API

# Get API credentials: Settings → API Users

# List subscribers:
curl https://newsletter.yourdomain.com/api/subscribers \
  -u "admin:your-password" | jq '.data.results[].email'

# Add subscriber:
curl -X POST https://newsletter.yourdomain.com/api/subscribers \
  -u "admin:your-password" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newuser@example.com",
    "name": "New User",
    "status": "enabled",
    "lists": [1],
    "preconfirm_subscriptions": true
  }'

# Get campaign stats:
curl https://newsletter.yourdomain.com/api/campaigns/42/stats \
  -u "admin:your-password" | jq '.data'

Maintenance

# Update Listmonk:
docker compose pull
docker compose up -d

# Backup database:
docker exec db pg_dump -U listmonk listmonk \
  | gzip > listmonk-db-$(date +%Y%m%d).sql.gz

# Logs:
docker compose logs -f listmonk

# Cleanup old analytics (optional, saves space):
# Settings → Maintenance → Purge analytics

See all open source email marketing and newsletter tools at OSSAlt.com/categories/marketing.

Comments