DEV Community

Cristian Tala
Cristian Tala

Posted on

How I Saved $348/Year with a Self-Hosted Newsletter

I pay $0/month for my newsletter. I used to pay $29/month on MailerLite.

Annual savings: $348.

Subscribers: 1,923.

Open rate: 32% (vs 21% industry average).

MailerLite isn't bad. It's excellent. The problem is that you don't need to pay for something you can self-host for free.

Here's how I migrated from MailerLite to Listmonk, how long it took, and why my open rate actually went up after the migration.

The Problem: Newsletter SaaS Is Expensive

Typical pricing (2026):

  • MailerLite: $29/month for ~2K subs
  • Mailchimp: $45/month
  • ConvertKit: $66/month

Annual cost: $348-$792.

For what? Sending emails, basic templates, stats (opens, clicks).

Reality: You can do ALL of that with open source software + a $12/month VPS (that you probably already have for other services).

The Solution: Listmonk (Self-Hosted)

Listmonk is an open source newsletter platform:

  • ✅ Unlimited campaigns
  • ✅ Unlimited subscribers
  • ✅ Customizable templates
  • ✅ Complete stats (opens, clicks, bounces)
  • ✅ Multi-list support
  • ✅ Segmentation
  • ✅ Complete API
  • ✅ Docker-ready

Cost: $0/month (just hosting, which you already have).

Setup time: 2 hours (one-time).

The Migration: MailerLite → Listmonk

1. Export from MailerLite

MailerLite dashboard → Subscribers → Export → CSV

Output: subscribers.csv with email, name, status, subscribed date.

2. Setup Listmonk (Docker)

version: '3.8'

services:
  postgres:
    image: postgres:15
    container_name: listmonk_db
    restart: always
    environment:
      POSTGRES_USER: listmonk
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: listmonk
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - listmonk_net

  listmonk:
    image: listmonk/listmonk:latest
    container_name: listmonk
    restart: always
    ports:
      - "9000:9000"
    depends_on:
      - postgres
    environment:
      LISTMONK_app__address: "0.0.0.0:9000"
      LISTMONK_db__host: postgres
      LISTMONK_db__port: 5432
      LISTMONK_db__user: listmonk
      LISTMONK_db__password: ${POSTGRES_PASSWORD}
      LISTMONK_db__database: listmonk
      LISTMONK_app__admin_username: ${ADMIN_USER}
      LISTMONK_app__admin_password: ${ADMIN_PASSWORD}
    networks:
      - listmonk_net

volumes:
  postgres_data:

networks:
  listmonk_net:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode
docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

3. Reverse Proxy with Caddy

listmonk.yourdomain.com {
    reverse_proxy localhost:9000
}
Enter fullscreen mode Exit fullscreen mode
caddy reload
Enter fullscreen mode Exit fullscreen mode

Result: https://listmonk.yourdomain.com with automatic SSL.

4. Import Subscribers

Listmonk UI → Create list → Subscribers → Import → Upload CSV → Map columns.

Result: 1,923 subs imported in 30 seconds.

5. Create First Campaign

Send a brief note to subscribers letting them know about the platform change. Nothing fancy — just transparency.

Results: Open Rate Actually Went Up

Metric MailerLite (before) Listmonk (after)
Open rate 28% avg 32% avg (+4%)
Click rate 6% 8% (+2%)
Deliverability 95% 97%

Why did it improve?

1. Plain Text Emails

MailerLite templates = heavy HTML with images and styled buttons.

My Listmonk setup = plain text with minimal HTML.

Result: ISPs trust plain text more (fewer spam flags).

2. Full Sender Control

MailerLite = shared IP pool (your deliverability depends on other users).

Self-hosted Listmonk = your own server, your own reputation.

Setup dedicated SMTP with proper SPF, DKIM, DMARC configuration.

3. No "Marketing Template" Vibes

MailerLite templates scream "EMAIL BLAST."

My Listmonk template = personal email with light formatting.

ISPs detect: Email from a real person > marketing blast.

Automation: OpenClaw + Listmonk API

Workflow:

  1. Publish blog post on WordPress
  2. OpenClaw detects new post (webhook)
  3. Extracts title, excerpt, URL
  4. Generates email version (using best writing model)
  5. Creates Listmonk campaign (API)
  6. Schedules for next day 9 AM
import requests

LISTMONK_URL = "https://listmonk.yourdomain.com"

campaign_data = {
    "name": f"Newsletter: {post_title}",
    "subject": generated_subject,
    "body": email_html,
    "lists": [1],  # List ID
    "type": "regular",
    "content_type": "html",
    "send_at": "2026-02-19T09:00:00Z"
}

response = requests.post(
    f"{LISTMONK_URL}/api/campaigns",
    json=campaign_data,
    auth=(LISTMONK_USER, LISTMONK_PASS)
)
Enter fullscreen mode Exit fullscreen mode

Result: Blog post → Newsletter (zero manual clicks).

Minimal Template (My Setup)

<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Georgia, serif; max-width: 600px; margin: 0 auto; padding: 20px; color: #333; }
    h1 { font-size: 24px; color: #000; }
    p { line-height: 1.6; margin-bottom: 15px; }
    .footer { color: #666; font-size: 14px; margin-top: 40px; }
  </style>
</head>
<body>
  <h1>{{ .Campaign.Name }}</h1>
  {{ .Campaign.Body }}
  <div class="footer">
    <p>You're receiving this because you subscribed at cristiantala.com.</p>
    <p><a href="{{ .UnsubscribeURL }}">Unsubscribe</a></p>
  </div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Key Lessons

Subject Lines Need a Human Touch

Tested AI-generated subject lines for 10 newsletters:

  • AI subjects: 24% open rate
  • Human subjects: 35% open rate

AI can write the body. Subject lines need human instinct.

Send Time Matters

  • 6 AM: 18% open (too early)
  • 9 AM: 35% open
  • 2 PM: 22% open (inbox already full)
  • 6 PM: 12% open (disconnection mode)

Consistency > Frequency

Before: Newsletter every 2 weeks (inconsistent).

After: Newsletter every Monday at 9 AM (clockwork).

Open rate went up 8% from consistency alone.

Why: Readers expect the email. If it arrives when expected, they open it.

Costs: $0 vs $348

Item MailerLite Listmonk (self-hosted)
Monthly $29 $0
Annual $348 $0
VPS n/a $12/mo (shared with 8 other services)
Setup time 0 2 hours (one-time)

Setup payback: Immediate (month 1 saves $29).

Hostinger VPS is what I use — enough power to run Listmonk, n8n, NocoDB, and 5 more services on a single instance.

When NOT to Self-Host

Don't self-host if:

  • <500 subs (free SaaS tiers work fine)
  • You don't know Docker (learning curve isn't worth $29/month)
  • You need advanced features (automatic A/B testing, ML segmentation)
  • You don't have a VPS (buying one just for a newsletter doesn't make sense)

Do self-host if:

  • 1,000+ subs (savings >$20/month)
  • You already have a VPS (marginal cost = $0)
  • You know basic Docker (or want to learn)
  • You value full control (data ownership, deliverability)

Migration Checklist

  • [ ] Export subscribers CSV from your current provider
  • [ ] Set up Docker (postgres + listmonk)
  • [ ] Configure Caddy reverse proxy
  • [ ] Verify SSL works
  • [ ] Create main list
  • [ ] Import subscribers CSV
  • [ ] Configure SMTP + SPF/DKIM/DMARC
  • [ ] Create email template
  • [ ] Send test campaign to yourself
  • [ ] Send migration notice to subscribers
  • [ ] Monitor opens/clicks for 3 campaigns
  • [ ] Cancel old provider after 1 month testing

Conclusion

Setup time: 2 hours (one-time).

Annual savings: $348.

ROI: $174/hour of setup work.

Bonus: Open rate went up, full deliverability control, data ownership, no vendor lock-in.

2 hours of setup. $348/year saved. Forever.

Do you use a self-hosted newsletter? Which platform? Share in the comments.

📝 Originally published in Spanish at cristiantala.com

Top comments (0)