Stop Paying Mailchimp! Self-Host Newsletters with listmonk
What if I told you that sending 100,000 emails could cost you exactly $0 in platform fees? No monthly subscriptions. No per-subscriber pricing tiers. No sudden price hikes that force you to choose between your newsletter and your rent.
Every developer who's built an audience knows the sinking feeling. You pour months into growing a mailing list, only to watch your email platform's bill balloon as your subscriber count climbs. Mailchimp charges $299/month for 10,000 contacts. ConvertKit? $290/month. And that's before you hit their sending limits or need features they lock behind higher tiers.
But what if the solution wasn't another SaaS product with a slick marketing page? What if the secret weapon top indie hackers and bootstrapped founders are using is something completely different—something you control, something that runs on your own infrastructure, something packed into a single binary that deploys in minutes?
Enter listmonk, the high-performance, self-hosted newsletter and mailing list manager that's quietly revolutionizing how technical founders think about email. Built by Kailash Nadh and backed by Zerodha (India's largest stock broker), this isn't some fragile side project. It's battle-tested infrastructure that handles millions of emails without breaking a sweat—or your budget.
In this deep dive, I'll show you exactly why developers are abandoning expensive SaaS platforms for listmonk, how to get it running in under 10 minutes, and the advanced techniques that separate amateur deployments from production-grade newsletter machines. By the end, you'll wonder why you ever paid for email software in the first place.
What is listmonk?
listmonk is a standalone, self-hosted newsletter and mailing list manager that defies every assumption about email software complexity. Unlike bloated marketing platforms that require teams of specialists, listmonk ships as a single binary file. One executable. That's it. No dependency hell, no microservice orchestration, no surprise infrastructure bills.
The project was created by Kailash Nadh, CTO of Zerodha, one of India's most respected technology companies. This pedigree matters—Zerodha processes millions of financial transactions daily, and Nadh built listmonk to solve real operational problems at scale. When a fintech CTO open-sources their internal tooling, developers pay attention.
Under the hood, listmonk is engineered for performance. The backend is written in Go, a language purpose-built for concurrent network operations. The frontend uses Vue.js with Buefy for UI components, delivering a modern dashboard that rivals any SaaS competitor. Data persistence happens through PostgreSQL, the gold standard for reliable, transactional data storage.
But here's what makes listmonk genuinely disruptive: it's AGPL v3 licensed. This isn't "open core" with features held hostage behind enterprise tiers. Every capability—API access, multi-list management, campaign analytics, subscriber segmentation—is fully open and self-hostable. You get the complete feature set that powers production deployments, not a crippled community edition.
The timing couldn't be better. As SaaS fatigue hits the developer community and data sovereignty becomes non-negotiable, listmonk represents a third path between expensive managed services and janky self-hosted alternatives. It's trending now because it solves the problem everyone assumed was unsolvable: enterprise-grade email infrastructure without enterprise-grade complexity or cost.
Key Features That Crush the Competition
Let's dissect what makes listmonk technically superior to platforms charging 50x more:
Single Binary Deployment The entire application compiles to one executable. This isn't marketing fluff—it's a fundamental architectural decision that eliminates deployment friction. No Node.js version conflicts, no Python environment management, no Docker image layer caching mysteries. Copy one file, run it, done.
Blazing Fast Performance Built on Go's goroutine concurrency model, listmonk handles massive subscriber lists without choking. While competitors throttle your sending speed based on your payment tier, listmonk's throughput is limited only by your SMTP relay and server resources. Users routinely report campaigns completing in minutes that took hours on SaaS platforms.
Modern Vue.js Dashboard The admin interface isn't an afterthought. Real-time campaign statistics, subscriber growth charts, bounce tracking, and engagement metrics render smoothly. The Buefy component library ensures responsive design that works on mobile devices without dedicated apps.
Advanced List Management Create unlimited mailing lists with custom fields, double opt-in workflows, and automatic bounce handling. Segment subscribers using SQL-like query expressions. Build suppression lists and manage unsubscribes with full GDPR compliance tools.
Template System with Full HTML Control
Unlike platforms that lock you into drag-and-drop editors with bloated output, listmonk gives you complete HTML template freedom. Use Go's html/template engine for dynamic content injection. Create plaintext alternatives automatically. Preview rendering across email clients before sending.
REST API & Webhooks Every operation is programmable. Import subscribers programmatically, trigger campaigns from CI/CD pipelines, sync unsubscribe events to your CRM. The API uses standard HTTP with JSON responses—no proprietary SDKs required.
Multi-Tenant Capabilities Run campaigns for multiple brands or clients from a single instance. Separate lists, templates, and analytics while sharing infrastructure costs. This alone replaces $500+/month multi-account SaaS setups.
Real-World Use Cases Where listmonk Dominates
The Bootstrapped SaaS Founder
You're pre-revenue with 5,000 beta users. Mailchimp's free tier just expired, and their paid plan would consume 30% of your runway. listmonk on a $5/month VPS handles your entire user communication stack—product updates, onboarding sequences, and churn recovery emails—leaving capital for actual product development.
The Privacy-First Organization
GDPR, HIPAA, or SOC 2 compliance requires data residency guarantees that SaaS platforms can't provide. With listmonk, subscriber data never leaves your infrastructure. Audit logs, encryption at rest, and network isolation are fully under your control. European universities and healthcare startups are adopting listmonk specifically for this reason.
The High-Volume E-commerce Operator
Sending 500,000 transactional and promotional emails monthly? SaaS pricing becomes punitive fast. listmonk paired with AWS SES or Postmark delivers equivalent deliverability at 1/50th the platform cost. One merchant reported saving $14,000 annually after migrating their Shopify email flows.
The Developer Content Creator
Your newsletter is your business. Substack takes 10% of subscription revenue forever. ConvertKit scales linearly with your success, punishing growth. listmonk lets you own the entire stack: capture emails on your domain, deliver through your infrastructure, and keep 100% of monetization. Popular tech newsletters like "Bytes" and "React Newsletter" have inspired developers to explore self-hosted alternatives.
The Internal Corporate Communicator
IT departments need to announce system maintenance, security updates, and policy changes to thousands of employees. listmonk integrates with LDAP/Active Directory for authentication, runs behind corporate firewalls, and eliminates third-party data processing agreements.
Step-by-Step Installation & Setup Guide
Ready to escape SaaS pricing? Here's every command to get production-ready in minutes.
Docker Deployment (Recommended)
The fastest path to running listmonk uses Docker Compose with the official image:
# Download the compose file to the current directory.
curl -LO https://github.com/knadh/listmonk/raw/master/docker-compose.yml
# Run the services in the background.
docker compose up -d
Navigate to http://localhost:9000 and complete the web-based setup wizard.
What happens behind the scenes: The compose file orchestrates two containers—listmonk itself and a PostgreSQL instance. Data persists in a Docker volume, so container restarts won't erase your subscriber lists. The up -d flag detaches the process, letting you close your terminal without stopping the service.
Production hardening: Before going live, edit docker-compose.yml to:
- Pin to a specific version tag instead of
:latest - Set PostgreSQL credentials via environment variables, not defaults
- Mount the database volume to a host directory for backup access
- Configure reverse proxy (nginx/Caddy) for TLS termination
Binary Installation (Maximum Control)
For bare-metal deployments or custom environments:
# Download the latest release from GitHub
wget https://github.com/knadh/listmonk/releases/latest/download/listmonk_linux_amd64.tar.gz
tar -xzf listmonk_linux_amd64.tar.gz
# Generate default configuration
./listmonk --new-config
This creates config.toml. Edit critical sections:
[app]
address = "0.0.0.0:9000"
admin_username = "secure_admin_name" # Never use 'admin'
admin_password = "long_random_string" # Generate with: openssl rand -base64 32
[db]
host = "localhost"
port = 5432
user = "listmonk"
password = "your_postgres_password"
database = "listmonk"
ssl_mode = "disable" # Set 'require' for production with valid certs
Initialize the database schema:
./listmonk --install
Critical note: The --install flag creates tables and seed data. Running it on an existing database is safe—listmonk uses idempotent migrations, so repeated runs won't corrupt data. For version upgrades, use ./listmonk --upgrade instead.
Finally, start the server:
./listmonk
Systemd service setup for production reliability:
# /etc/systemd/system/listmonk.service
[Unit]
Description=listmonk newsletter manager
After=postgresql.service
[Service]
Type=simple
User=listmonk
WorkingDirectory=/opt/listmonk
ExecStart=/opt/listmonk/listmonk
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Enable with: sudo systemctl enable --now listmonk
REAL Code Examples: Power User Patterns
These patterns come directly from production listmonk deployments and the official documentation.
Example 1: Custom Subscriber Import Script
Bulk importing from a legacy system or CRM? Use the REST API with proper error handling:
import requests
import csv
from urllib.parse import urljoin
LISTMONK_URL = "https://newsletter.yourdomain.com"
USERNAME = "your_admin_user"
PASSWORD = "your_secure_password"
LIST_ID = 1 # Target list ID from dashboard
def import_subscribers(csv_path):
"""Batch import subscribers with custom attributes."""
session = requests.Session()
session.auth = (USERNAME, PASSWORD)
with open(csv_path, 'r') as f:
reader = csv.DictReader(f)
subscribers = []
for row in reader:
subscriber = {
"email": row['email'],
"name": row.get('name', ''),
"status": "enabled",
"lists": [LIST_ID],
"attribs": {
"plan": row.get('subscription_tier', 'free'),
"signup_source": row.get('source', 'migration'),
"mrr": float(row.get('monthly_revenue', 0))
}
}
subscribers.append(subscriber)
# Batch in groups of 1000 to prevent timeout
if len(subscribers) >= 1000:
_submit_batch(session, subscribers)
subscribers = []
# Submit remaining
if subscribers:
_submit_batch(session, subscribers)
def _submit_batch(session, subscribers):
"""Submit batch with retry logic for resilience."""
endpoint = urljoin(LISTMONK_URL, "/api/subscribers")
for attempt in range(3):
try:
response = session.post(
endpoint,
json={"subscribers": subscribers},
timeout=30
)
response.raise_for_status()
print(f"Imported {len(subscribers)} subscribers")
return
except requests.exceptions.RequestException as e:
if attempt == 2:
raise
time.sleep(2 ** attempt) # Exponential backoff
# Execute migration
import_subscribers("legacy_subscribers.csv")
Why this matters: The attribs field stores arbitrary JSON—enabling powerful segmentation without database schema changes. The batching prevents API timeouts on large imports, and exponential backoff handles transient failures gracefully.
Example 2: Dynamic Campaign Template with Go Templates
listmonk uses Go's html/template engine for powerful personalization:
<!-- Template: weekly_digest -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{ .Campaign.Subject }}</title>
</head>
<body>
<h1>Hey {{ .Subscriber.Name | default "there" }}!</h1>
{{ if eq .Subscriber.Attribs.plan "premium" }}
<div class="premium-banner">
<p>⭐ Premium perk: Your exclusive content awaits below</p>
</div>
{{ end }}
<div class="content">
{{ .Campaign.Content }}
</div>
{{ if gt .Subscriber.Attribs.mrr 100.0 }}
<div class="vip-section">
<h3>High-value customer resources</h3>
<p>Based on your {{ .Subscriber.Attribs.mrr | printf "%.2f" }}/month spend,
you're eligible for priority support.</p>
</div>
{{ end }}
<footer>
<p>Sent to {{ .Subscriber.Email }} |
<a href="{{ .UnsubscribeURL }}">Unsubscribe</a> |
<a href="{{ .OptinURL }}">Update preferences</a>
</p>
</footer>
</body>
</html>
Template power explained: The {{ .Subscriber.Attribs }} object exposes custom fields for conditional content. The default pipeline prevents empty-name awkwardness. printf "%.2f" formats currency values. These built-in functions eliminate pre-processing scripts that other platforms require.
Example 3: Webhook Handler for Real-Time Sync
Keep external systems synchronized with subscriber events:
// Node.js Express webhook receiver for listmonk events
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = process.env.LISTMONK_WEBHOOK_SECRET;
app.use(express.raw({ type: 'application/json' }));
function verifySignature(payload, signature) {
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
app.post('/webhooks/listmonk', async (req, res) => {
const signature = req.headers['x-listmonk-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body);
switch (event.type) {
case 'subscribe':
await activateTrial(event.data.subscriber);
break;
case 'unsubscribe':
await deactivateMarketing(event.data.subscriber.email);
// Trigger churn survey email via separate transactional path
await enqueueChurnSurvey(event.data.subscriber);
break;
case 'campaign.sent':
await updateAnalyticsDashboard({
campaignId: event.data.campaign.id,
sentAt: event.data.sentAt,
recipientCount: event.data.subscriberCount
});
break;
case 'bounce':
await flagForReEngagement(event.data.subscriber.email);
break;
}
res.status(200).json({ received: true });
});
async function activateTrial(subscriber) {
// Provision 14-day trial in your SaaS application
await fetch('https://api.yourapp.com/trials', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.API_KEY}` },
body: JSON.stringify({
email: subscriber.email,
source: 'newsletter_signup',
attribution: subscriber.attribs?.signup_source || 'direct'
})
});
}
app.listen(3000, () => console.log('Webhook server listening'));
Production consideration: Always verify webhook signatures to prevent spoofing. The timingSafeEqual comparison prevents timing attacks that could leak signature validity. Process webhooks asynchronously (queue to Redis/Bull) for reliability under load.
Advanced Usage & Best Practices
Database Optimization Subscriber tables grow fast. Add partial indexes on frequently queried segments:
CREATE INDEX idx_active_subscribers
ON subscribers(email)
WHERE status = 'enabled' AND created_at > NOW() - INTERVAL '1 year';
SMTP Relay Strategy Never use your server's IP directly. Configure reputable relays:
- AWS SES: $0.10 per 1,000 emails, excellent deliverability
- Postmark: $1.50 per 1,000, with superb transactional reputation
- Mailgun: Flexible routing for complex domains
Rotate between multiple SMTP configs for different campaign types (transactional vs. marketing) to protect sender reputation.
Monitoring & Alerting
Export Prometheus metrics by enabling the endpoint in config.toml:
[app]
enable_public_subscription_page = true
# ...
Track critical metrics: bounce rate (>5% triggers review), unsubscribe rate (>1% indicates content mismatch), delivery latency spikes.
Backup Strategy PostgreSQL logical dumps aren't enough for point-in-time recovery. Enable WAL archiving:
# In postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'cp %p /backups/wal/%f'
Rate Limiting Wisdom Respect recipient servers. Configure per-domain throttling:
[app]
concurrency = 10 # Simultaneous connections
message_rate = 100 # Per-second maximum
Gmail accepts bursts differently than corporate Exchange servers—tune based on your subscriber domain distribution.
Comparison with Alternatives
| Feature | listmonk | Mailchimp | ConvertKit | Sendy | Mailtrain |
|---|---|---|---|---|---|
| Pricing Model | Free (self-hosted) | $299/mo (10k contacts) | $290/mo (10k contacts) | $69 one-time | Free (self-hosted) |
| Deployment | Single binary / Docker | SaaS only | SaaS only | PHP/MySQL | Node.js/MongoDB |
| Database | PostgreSQL | Proprietary | Proprietary | MySQL | MongoDB |
| API | Full REST | Full REST | Full REST | Limited | Limited |
| Template Engine | Go templates | Drag-drop + HTML | Limited HTML | PHP | Handlebars |
| Multi-list Management | Unlimited | Paid tiers | Paid tiers | Unlimited | Unlimited |
| Subscriber Segmentation | SQL expressions | GUI builder | Basic tags | Basic | Basic |
| Performance at Scale | Excellent | Throttled by plan | Throttled by plan | Good | Moderate |
| Data Sovereignty | Complete | None | None | Complete | Complete |
| Active Development | Very active | Corporate | Corporate | Slow | Moderate |
The verdict: listmonk dominates for technical users who value control and cost efficiency. Mailchimp/ConvertKit win for non-technical teams needing hand-holding. Sendy is cheaper but requires PHP expertise and lacks modern architecture. Mailtrain's MongoDB dependency creates operational complexity that listmonk's PostgreSQL avoids.
FAQ
Is listmonk really free for unlimited subscribers? Absolutely. The AGPL v3 license imposes no usage restrictions. Your only costs are infrastructure (typically $5-50/month VPS) and SMTP relay fees (often $0.0001 per email with SES).
How does deliverability compare to Mailchimp? Deliverability depends on your SMTP relay's reputation, not listmonk itself. Using AWS SES or Postmark achieves equivalent or better inbox placement than Mailchimp's shared IPs, especially as your volume grows and you earn dedicated IP reputation.
Can I migrate from Mailchimp/ConvertKit without losing subscribers?
Yes. Export CSV files from your current platform, then use listmonk's API or admin import tools. Custom fields map to attribs JSON. Re-create templates using listmonk's HTML editor with Go template syntax.
Is listmonk suitable for non-technical users? The admin dashboard is intuitive, but initial deployment requires command-line comfort. Consider managed hosting options or Docker-based one-click deploys for teams without DevOps resources.
How do I handle bounces and complaints? listmonk automatically processes bounces via webhook integrations with SES/Postmark. Hard bounces suppress addresses immediately; soft bounces retry with exponential decay. Complaint feedback loops update suppression lists in real-time.
What's the maximum list size listmonk can handle? Production deployments manage millions of subscribers. Performance bottlenecks typically emerge at the SMTP relay level, not listmonk itself. PostgreSQL tuning (connection pooling, query optimization) becomes important beyond 10 million contacts.
Does listmonk support A/B testing? Native A/B testing isn't implemented yet. Workaround: create duplicate campaigns with variant subjects/content, split subscribers via list segmentation, and compare analytics manually. The feature is on the roadmap.
Conclusion
The email industry has operated on a simple deception: that sending messages requires expensive middlemen. listmonk exposes this lie with elegant engineering. A single binary. A modern dashboard. Complete data ownership. Zero platform fees forever.
I've walked you through the architecture decisions that make this possible, the deployment patterns that get you production-ready in minutes, and the advanced techniques that transform basic installations into newsletter powerhouses. The code examples work today, the comparisons are brutally honest, and the path forward is clear.
But reading about listmonk isn't the same as experiencing it. The live demo at demo.listmonk.app lets you explore the dashboard without commitment. When you're ready to claim independence from SaaS pricing, the repository awaits:
Star listmonk on GitHub — clone it, deploy it, and join the growing community of developers who refuse to pay ransom for their own audience relationships.
Your subscribers belong to you. Your data belongs to you. Your infrastructure should too. Stop paying Mailchimp. Start owning your newsletter.