Popular newsletter services like Mailchimp are now charging lots of money for sending emails, and people are –understandably so– looking for free alternatives. I already went down this rabbit hole, and I already wrote a blogpost comparing the best options out there, and I found out Listmonk to be the best free newsletter service out there.

Listmonk is a free, open source and self-hosted newsletter & mailing list manager.

Essentially, it’s free software you can use to manage and publish your newsletter and send transactional emails to your users. It works, it’s beautiful, and it’s powerful.

Listmonk main dashboard

You can try a live Listmonk demo here.

Listmonk is an ideal free alternative to Mailchimp – but it’s self-hosted, meaning you need your own server and need to follow a few technical steps to get it up and running. But once you do, you can run your email marketing for cheap or free, no matter the size of your newsletter. You will only pay at-cost for the amount of emails you send using a mailing service like Postmark or AWS SES (roughly, $1 for every 10k emails)

(Note: I’m not affiliated to Listmonk in any way. This post is not paid promotion. I just found about Listmonk while searching for free email newsletter services, and fell in love with it. I found it difficult to get everything up and running, that’s why I made this tutorial so I can remember how to do it for the next time I have to setup a Listmonk instance, and it may help others in my same situation too)

Let’s set up a fully-functional, production-ready Listmonk instance:

Time needed: 1 hour and 30 minutes.

Heads up: this guide will get quite technical. If you’re not a developer yourself, you’ll probably need help from one to set everything up. You can email this guide to your developer.

  1. Get a server

    Any VPS running Ubuntu Server will do. I recommend using Hetzner or Digital Ocean. The cheapest, most basic instance will do. You can run this on something as low as 500Mb RAM, 1vCPU.

    Another popular option is AWS Lightsail. AWS Lightsail offers cheap servers (instances) for as low as $3.5/mo. They have a few more locations than Hetzner or Digital Ocean, and the account creation process is slightly faster and simpler, so we’ll use that – but again, any server running Ubuntu Server 22.04 LTS will do.

    To start, head to AWS Lightsail. Click on “Create instance” and choose a Linux/Unix machine. Select “Operating System (OS) only”. Select Ubuntu 22.04 LTS.

    Pick a plan (the $3.5/mo or $5/mo should be enough) and hit “Create instance”.

    Make sure to review your instance details so it runs in the region / location you want (rule of thumb: keep your servers closest to where the majority of your users are), and make sure it uses the correct SSH key pair so you can log into the machine.

  2. Attach a public static IP address to your instance

    By default, your AWS Lightsail instance comes with a dynamic IP address. This means every time your server gets rebooted it will pick up a new IP address. This is not ideal, as you’ll always need to type a different IP address if you want to connect to your instance.

    Click on your new instance and you’ll get to the instance management page. Navigate to the “Networking” tab and click on “Attach static IP” under its Public IP address box. Follow the steps to attach the static IP. At the end, you’ll see how your instance’s IP address has been fixed to the new static IP address. This IP will now remain unchanged no matter how many times you reboot your server.

  3. Allow HTTPS traffic to your instance

    We’ll be installing an SSL certificate and accessing our Listmonk installation via HTTPS. To do that, we need to add a firewall rule on our Lightsail instance so it accepts HTTPS traffic.

    Head to the “Networking” tab of your instance, and under “IPv4 Firewall”, click “Add rule”. In the Application field, choose “HTTPS” and click “Create”. The instance will now allow HTTPS traffic.

  4. Connect via ssh to your instance

    Let’s ssh into your instance so we can start messing around.

    The default user for Ubuntu Server instances is ubuntu, so let’s log in as that:

    ssh ubuntu@your-static-public-ip

    Make sure to replace your-static-public-ip with the IP you’ve just created and make sure you’re using the same SSH key pair you used while launching the instance – and you should be in!

  5. Set up the server to run Docker containers in production

    I’ve created a shell script that takes a fresh Ubuntu Server image and makes it into a production-ready machine to run Docker containers securely. It hardens the security of the machine and follows best practices to run containers in production with real internet traffic.

    Note: you shouldn’t just run random scripts that random people you don’t know (like me) have posted on the internet: it’s a good practice to always examine them (here) – one trick is to run them by an LLM like ChatGPT or Claude to quickly check if they’re safe to run or not, if you’re not really sure of what they do.

    To run it, just run the following commands:

    wget https://gist.githubusercontent.com/rameerez/f92ada0c83c2ecf9654cbadbc6adbbca/raw/21e74ad93740df363f344a3c40ca54a59a9b5168/docker-host-production-setup-ubuntu-server.sh

    chmod +x ./docker-host-production-setup-ubuntu-server.sh

    sudo ./docker-host-production-setup-ubuntu-server.sh

    After running, it should show you a successful message:

  6. Configure your DNS to link your domain to your instance so we can install Listmonk

    Our instance is now represented by just an IP. Sure, we could connect to Listmonk using the IP alone, but since we’re configuring this for production, and we’ll be adding an SSL certificate to make everything secure – we need to link our instance to a domain name or subdomain.

    Head to your DNS server settings (I’m using Cloudflare) and point a subdomain (usually something like newsletter.example.com) to your instance’s IP address using an A record.

    The exact steps will vary depending on what service you’re using for DNS (Cloudflare, Namecheap, Name.com, etc.), but it will look something like this. Save and exit.

  7. Install Listmonk

    I’ve also created another handy script to install Listmonk using Docker to our freshly configured Ubuntu Server instance. Just run it:

    wget https://gist.githubusercontent.com/rameerez/6bf916df597f0e663fbb21816c8a1b90/raw/93180f406218c902583c6b4b97dd0942c84b6dcf/listmonk-setup.sh

    chmod +x ./listmonk-setup.sh

    sudo ./listmonk-setup.sh

    It will ask you for the domain where you want to install Listmonk. This is the domain or subdomain we’ve configured in the previous step, let’s assume it’s newsletter.mydomain.com

    Write your domain when the script asks for it and press enter. The script will then start installing Listmonk in the machine.

    Follow the steps you will see on screen and Listmonk should be installed:

  8. Access the Listmonk web interface

    Open your favorite web browser and head to your domain newsletter.mydomain.com

    You should see the Listmonk login screen. If you click “Login”, a popup will ask for your credentials.

    Your Listmonk credentials were shown in the last few messages after running the Listmonk installation script (see the step above), but if you’ve missed it, they can always be found and changed in the config.toml file under /opt/listmonk/config.toml

    Enter your credentials and you should be logged in to the Listmonk admin dashboard

  9. Configure Listmonk basics

    Log in to Listmonk, navigate to the Settings panel and set up the newsletter general info.

    In particular, make sure to set up the Root URL to https://newsletter.mydomain.com or your links and all email tracking will be broken (make sure to include the https://)

    Make sure to change the default ‘from’ email too, and set it to an address under the domain where you intend to send mails from.

    Since you’re at it, also set up the public appearance of your newsletter by providing a newsletter name and logo your users will see on public pages like signup forms, archive and unsubscribe page.

  10. Create SMTP credentials to send emails using AWS SES

    Amazon Simple Email Service (SES) is an Amazon AWS service that allows you to send email using Amazon AWS’s email servers. This is important because email servers need to have a good reputation to be able to actually deliver emails, and using your own server as your email server will probably not work.

    You can also use Mailgun, Mailjet, Sendgrid or Postmark to send emails, but since we’re already using Amazon for Lightsail, we’ll just use SES.

    It’s quite cheap: you’ll pay $1 for every 10k emails you send ($10 for 100k emails; $100 for 1M emails, etc.)

    Head to AWS SES, select the region from which you want to send emails and follow the steps to create an identity. A verified identity is a domain, subdomain, or email address you use to send email through Amazon SES. Identity type would be “Domain”, and this would be mail.example.com in our example.

    In “Advanced DKIM settings”, select “Easy DKIM” and follow their steps to set up the right DNS records. It might be a bit cumbersome, but it’s pretty straightforward.

    Once you’re done setting up your domain in AWS SES, head to the AWS SES left panel, where it says “SMTP settings”, and click the button “Create SMTP credentials”.

    Follow the steps and you’ll get your SMTP Security Credentials. Don’t close this page, as you’ll need to copy these credentials in a second.

  11. Configure Listmonk to send email through AWS SES

    Now, back in Listmonk, head to the “SMTP” tab in Settings to configure AWS SES.

    Click on “Amazon SES” as indicated below, so the necessary fields get pre-filled.

    Modify the “Host” with your AWS SES region (mine is us-east-1, you can see it in your AWS url, like: https://us-east-1.console.aws.amazon.com)

    And paste the username and password from the AWS SES Credentials page.

    Make sure to use port 2465 for SMTP. The default port for SMTP is 465, but some server providers like Hetzner simply block port 465 on their whole network to prevent email spam and email abuse; and AWS SES accepts SMTP on port 2465, so let’s just use that.

    Click “Test Connection” and everything should work just fine!

  12. Increase your AWS SES sending quota

    If you’re using this in a production setting (that is, you’ll be actually sending hundreds or thousands of emails), you’ll probably want to ask AWS to raise your SES sending quota.

    By default, you can only send 200 emails per day on SES. This can be raised for free after AWS examines your case and authorizes you to send more emails (they do this to prevent spam).

    You need to follow these steps to use their Service Quotas Console to open a case and explain your request. They’ll demand to know a few details about the content and nature of your emails and how you’re collecting addresses and users’ consent to be messaged.

  13. [Optional] Configure Listmonk to store attachments in AWS S3

    Attatchments can be stored in the same server you’re running Listmonk on, but we can do better. Let’s store them on AWS S3 for better availability, replication and to avoid overloading our Listmonk server.

    Amazon Simple Storage Service (S3) is an AWS service that allows you to store files. It’s kind of like Dropbox: you have “folders” (buckets), you upload files to them and they are stored in the AWS cloud so you can access them from anywhere. Some of these buckets can be public, so anyone in the world can access the files you upload (ideal for storing things like images in your newsletter attachments).

    Head to AWS S3 and create a new bucket. Set “ACLs enabled” with “Bucket owner preferred” and disable the “Block all public access” checkbox.

    Now, we need a different AWS user for the Listmonk app to access our S3 bucket. We’ll only grant this new user programmatic access via an access key, and we’ll create a strict policy so it only can access the S3 bucket we’ve just created.

    Head to AWS IAM, and create a new user. Name it something like listmonk-s3. Do not “Provide user access to the AWS Management Console”. In permissions, click “Attach policies directly” and click on the “Create policy” button.

    Instead of using the Visual Editor, you can just copy and paste the JSON policy I’ve just created for this purpose. Make sure to edit it and replace listmonk-bucket to the exact name you’ve given to your S3 bucket.

    Create the policy, attach it to your new user, finish creating the user and open their user page in IAM. Navigate to their “Security credentials” tab, scroll down a bit to where it says “Access keys” and create a new access key for the user. Select “Application running outside AWS” and hit next until you can create and get your access key.

    Once you have your Access key and Secret access key, don’t close the window – open the Listmonk settings in a new window and navigate to the “Media upload” tab. Fill up the information as shown below, make sure to edit your own region, and your own bucket name.

  14. Send a test email campaign

    Make sure everything is working by creating a test campaign with an image and sending it to yourself. If you’ve followed the steps correctly, a new test email from your newsletter should have arrived in your inbox!

  15. [Optional] Configure bounces and blocklist bad emails

    Handling bounces and complaints is key if you don’t want your mail-sending account banned. Services like AWS SES take bounces and complaints very seriously, and require you to stay below a certain threshold of complaints, or they’ll stop you from sending emails.

    You get bounced emails when you send emails to addresses that don’t accept any emails (like fake addresses, mistyped addresses, or addresses that have blocked incoming email). You get complaints when users report your email as spam.

    If you don’t react to this and keep sending emails to addresses that either bounce or complain about your emails, your percentage of bounced and complained emails over total emails sent will keep increasing, and your account will get banned.

    The best way to react to bounces and complaints is to add conflictive email addresses to the blocklist, so you don’t send them any more emails in the future.

    This is a feature that comes with Listmonk, but it requires us to set it up correctly in Amazon AWS so that AWS SES knows how to notify our Listmonk instance when there’s a bounce or complaint.

    The first thing we want to do is to go to the “Bounces” tab of our Listmonk settings and toggle on “Enable bounce processing”, “Enable bounce webhooks”, and “Enable SES”. Click “Save”.

    Then, we need to do a few cumbersome, but easy, steps to link AWS SES to Listmonk. The official Listmonk docs explain in good detail how to do it, please follow their steps now. In a nutshell: you need to create a SNS “Standard” topic, create a HTTPS subscription to that topic with endpoint https://newsletter.mydomain.com/webhooks/service/ses, and add that subscription to SES under “Feedback notifications” both for “Bounce feedback” and “Complaint feedback”.

    If you’ve set it right, bounce and complain addresses will get added to the blocklist and unsubscribed from your mailing lists.

    One way to test it is to manually add [email protected] and [email protected] to your mailing list and send them a test email campaign (or campaign preview test). If everything is working, you’ll see the bounces in Listmonk under Subscribers > Bounces, and you’ll see the offending emails being automatically added to the blocklist in the subscribers list under Subscribers > All subscribers

And that’s it!

You are now able to use Listmonk as a Mailchimp replacement to manage your email marketing campaigns and send emails to thousands of addresses for pennies.

The total cost of this setup is around $5/mo for the server and $1 for every 10k emails sent, which means you can run the whole thing for less than a cup of coffee a month if you don’t send many emails. This makes Listmonk on AWS a great Mailchimp alternative for nonprofits, for example – or any other kind of project running on a tight budget. You can send transactional emails, marketing emails, create and grow a newsletter – and even send SMS / texts, Whatsapp messages, and more!

Please let me know in the comments if you got it up and running! It’s nice to hear this tutorial was useful to other people, and your feedback will make this guide even better!

P.S.: Follow me on Twitter to stay in the loop. I'm writing a book called Bold Hackers on making successful digital products as an indie hacker. Read other stories I've written. Subscribe below to get an alert when I publish a new post:

No spam ever, unsubscribe with one single click.

  1. Mark
    Apr 24, 2023

    Thank you for this great tutorial!!

    I actually needed the NGINX stuff to make my listmonk run on a sub domain, but all is written nicely and helpful.

    Reply
    • rameerez
      Jul 29, 2024

      Thanks! Happy you found it useful.

      Reply
  2. Someone
    May 06, 2023

    how to do the firewall for listmonk? If you don’t config the firewall rules. Anyone can get your subscription list through the GET APIs

    Reply
    • rameerez
      Jul 30, 2024

      I believe you’re confusing terms here: the firewall (ufw, or Lightsail’s own firewall) doesn’t have anything to do with what you’re asking. What you’re worried about is API authentication, and it turns out most of the API endpoints in Listmonk are in fact authenticated and password protected.

      If you try, for example, getting /api/subscribers on a browser window that doesn’t have a Listmonk session logged in (try an Incognito window, for example), you’ll just see the username / password prompt, and no results. The official docs on the Listmonk API are quite good: https://listmonk.app/docs/apis/subscribers – you’ll see all endpoints that need auth requiring username and password in the curl call.

      One endpoint that does not require auth, for example, is POST /api/public/subscription, which is used by the embeddable newsletter suscription form so that people can sign up for your (public) newsletter.

      Reply
  3. beefhooker
    Sep 28, 2023

    Hey rameerez! Thanks for the write-up. I’m interested in doing the same thing you are. How is your set-up doing seven months later? Any hiccups or issues you’ve run into since then, or has everything been going smoothly?

    Reply
    • rameerez
      Sep 29, 2023

      Honestly, apart from a couple of issues we haven’t been using it much!

      I remember having some problems with hitting the AWS SES quota, and having BIG problems sending the mail in a escalonated way. Like, we don’t wanna send 100k emails at once, we might want to try first with 100 or 1000 emails and see how it goes, if it gets delivered, if people like it, etc. before rolling it out to the whole DB. And for that we needed to make custom mailing lists of all users minus the 1000 experiment emails or something. It’s just unconvenient and I’m not sure Mailchimp or any other product solves it. Maybe just us being too cautious?

      Also we got delivery problems in our second issue I think. Like, the first issue would get normal open rates and then the second one would drop to almost zero. Maybe Gmail tagging our mails as spam or something because we’re coming from AWS? I ran email deliverability tests and everything seemed fine but this remained a mystery.

      Keeping your DB in sync with the newsletter is also something you need to do via their API (adding new subscribers, updating email consent, etc.)

      TL;DR: is this Listmok thing a good Mailchimp alternative? Yes, I think. But only if you know what you’re doing, if you really wanna run a big newsletter and if you know and have time to solve the problems that will come along the way. For any other use case I’d choose a hosted service like Mailchimp. Especially if your newsletter is small-ish

      Reply
  4. Jorge
    Mar 13, 2024

    Amazing post.
    Is it possible that you have a tutorial to handle listmonk bounces and be able to blocklist them?
    Also, do you know a tutorial on how to access the tutorial to track who accessed the email or clicked on the links?
    Again, thanks a lot for this amazing post with so clear instructions.

    Reply
    • rameerez
      Jul 29, 2024

      Just added an extra step explaining how to deal with bounced emails and complaints to automatically blocklist offending email addresses, thanks for the feedback!

      Reply
  5. ionds
    Apr 01, 2024

    That last step 14 doesn’t work. Adding images with Listmonk results in 403 access forbidden. Did the step twice.

    Reply
    • rameerez
      Jul 29, 2024

      Just fixed it!

      Reply
  6. kontur
    Apr 03, 2024

    You screenshot in step 14 shows the Bucket type as “Public” — as described above it will not work with “Public” but in fact requires “Private” as per the IAM and access setup described earlier.

    Reply
    • rameerez
      Jul 29, 2024

      Fixed, thanks!

      Reply
  7. Hadi
    Aug 28, 2024

    Great guide. Issue is i successfully installed and it was logged in correctly but after 10 to 15min it is unaccessible, and giving me this error 502 Bad Gateway. Is there any way to sort this out? I also rebooted the lightsail after this error. but still same

    I am using Lightsail US region, SES is on London, now after running sometime listmonk is not accessible from webbrowser, after restarting it will work fine again, but why it is stopping again and again. any solution?

    Reply
    • rameerez
      Sep 03, 2024

      I suggest you check the Listmonk logs / web server logs for info on why it’s failing. ChatGPT or Claude can help you navigate this: ask them where the logs probably are, how to interpret the error messages you see, how to fix them, etc.

      My only guess without more information would be that your instance is too small, and it doesn’t have enough memory / CPU to run the Listmonk service for a sustained period of time. But that’s only one guess. You’ll find the right solution after figuring out what the error is by reading the logs.

      Reply
    • Michael
      Nov 27, 2024

      I had a similar thing happen to me. I suspected Rameerez was right about his memory suspicion, but oddly Lighsail doesn’t display your RAM usage in the metrics tab even though it’s the first parameter of the product tiers. There were probably other ways to solve this, but I created a snapshot of my instance, spun up a larger capacity instance from that snapshot, and added in CloudWatch monitoring to keep an eye on the RAM usage. So far, things are running smoothly, but we’ll see when I get out of sandbox if 2GB of RAM was enough.

      Reply
      • rameerez
        Dec 02, 2024

        Thanks for sharing, Michael! You may also want to try Hetzner or DigitalOcean (both alternatives to AWS Lightsail) – they offer cheap instances and good monitoring too.

        Reply