Ghost charged me $174/month to manage my 10,000 subscribers.
ConvertKit used to charge me $100/month to manage the same 10,000 subscribers.
Now, my self-hosted Listmonk costs me less than $11/month to manage 10,000 subscribers.
For a small creator like myself, who does not make any revenue from YouTube, Medium, or his website, saving around $2,000/month is a big deal.
In this blog post, I will go over how I migrated from Ghost to Listmonk, how I hosted Listmonk on my server, and how I started sending my newsletters for free.
Here’s a quick lookahead:
- What is Listmonk?
- Listonk Feature Set
- Docker Installation
- Self-hosting with Nginx Proxy
- SMTP Setup with Gmail
- Limiting access with Firewalls
If that looks interesting to you, let’s get started.
Listmonk is a self-hosted emailing newsletter and mailing list manager.
Two of its most important features to me are open-source and private. You own your data. When you self-host Listmonk, it uses a Postgres database out of the box that stores all user data.
Whether you want to manage a mailing list of subscribers or design and send out personalized email campaigns, you can do it all with Listmonk.
Previously, I used both ConvertKit and Ghost as my mailing list manager. They were both expensive, but at the same time, their feature set was vast.
When I migrated to Listmonk, to my surprise, I found its features comparable to its more expensive counterparts.
Some notable highlights:
Store custom JSON attribute per subscriber
Both single and double opt-ins
Design personalized templates
Out-of-the-box API support for mailing list management as well as email campaigns
Supports multiple “message” types — email, SMS, Whatsapp, etc
User-friendly analytics dashboard
Public customizable “subscribe” page
Easy way to unsubscribe or wipe user data
Email views and clicks tracking
In my opinion, these will take you a long way — maybe even up to 100K subscribers — before you need a more complex (or expensive) solution.
If the feature set and price sound good to you, let me walk you through how I self-hosted Listmonk.
If you visit https://newsletter.irtizahafiz.com, you will find my Listmonk instance.
I host my Listmonk instance on my Digital Ocean server, which is also home to my personal website and web analytics dashboard.
All of those (and more) cost me around $11/month.
I found that the easiest way to get started with a production-ready version of Listmonk is through Docker.
The installation docs does a great job walking you through the steps. With one bash script, you will get the following:
Downloads necessary Listmonk Docker images and sets up containers
Runs a Postgres database container and sets up required schemas
Runs Listmonk admin UI, giving you full control locally
Once the docker container is up and running, you can access your Listmonk dashboard through your server’s port 9000.
So, if you go to http://localhost:9000, you can access your Listmonk dashboard.
However, I wanted to expose my instance to the public internet (of course behind a strong password). To do that, I used Nginx reverse proxy.
server {
listen 443 ssl;
server_name newsletter.irtizahafiz.com;
location / {
proxy_pass http://127.0.0.1:9000;
}
location = /api/public/subscription {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'POST';
add_header 'Access-Control-Allow-Headers' 'Content-Type';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
}
proxy_pass http://127.0.0.1:9000;
}
ssl_certificate /etc/letsencrypt/live/irtizahafiz.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/irtizahafiz.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
listen 80;
server_name newsletter.irtizahafiz.com; # Redirect HTTP requests to HTTPS
if ($scheme != "https") {
return 301 https://$host$request_uri;
}
}
The above Nginx configuration does the following:
Routes (or proxies) any HTTP or HTTPS request from newsletter.irtizahafiz.com to the server’s port 9000 (where Listmonk is running)
Adds the relevant CORS headers to allow cross-origin requests so that my website’s client can make API requests to manage Listmonk mailing lists and newsletters
Allows POST requests to create new subscribers (still requires AUTH headers)
With the above setup, I can do both:
Access the Listmonk dashboard from the public internet to create email campaigns, manage mailing lists, and check analytics
Access Listmonk’s API to programmatically interact with subscriber and campaign data
When it comes to sending emails out, Listmonk requires an SMTP client.
I could have used my personal domain’s SMTP, but it’s likely emails coming from my domain might be categorized as spam.
Instead, I chose a more reputable SMTP client — Gmail.
Through the Listmonk dashboard settings, it took me 2 minutes to link my business Gmail account. Now, all email campaigns Listmonk sends use my business Google email, and it’s FREE.
In the future, I plan on migrating from Gmail to Protonmail, but currently, Protonmail only supports SMTP in its business plan, which I don’t use.
Given that Listmonk’s dashboard is very powerful, it’s sufficient for all my needs.
Because of that, the only use I make of its API is to add new subscribers. To be more specific, for https://irtizahafiz.com/newsletter to make POST requests to add new subscribers to my Listmonk’s Postgres database.
So, in the next couple of days, I plan on limiting API access to that route only. It only takes a few lines of Nginx code, but I am lazy.
If you have made it this far, I hope you found this valuable.
Feel free to follow me on Medium, subscribe to my website, or follow me on YouTube.