sptr.net
    • about sptr.net
    • about me
  • Stories
    • NixImagery
    • Poetry
    • Tales of the Family
  • Pictures
    • NixImagery on Flickr
    • NixImagery on 500px
    • Wild Aye Photography
  • Tech
    • I was in the REME, I’ll have you know
    • Technical blog
  • Research
    • About my research
    • ORCID profile
    • Digital Education profile
    • The BBC’s Something Understood

    • PhD/Supervisors (login required)
    • Thesis (in work, login required)
    • Participants (login required)
    • Web services structure
    • Cotman Paints
    • Pixie Puzzles

On this page

  • Export subscriber details from SubStack
  • Prepare subscriber import file
  • Install listmonk
  • Configuration
  • Ready to play
  • Going outdoors
  • Up and running

Moving from SubStack to listmonk

Taking my SubStack subscribers to a new self-hosted email service
Published

12th November 2025

Modified

12th November 2025

I publish an infrequent newsletter of recent posts and stories from some of my travels and adventures. It used to be on SubStack but that place seems to have gone a bit sour for subscribers lately and I’ve fallen out of love with it. I still read stuff there but no longer post. All of my stuff pretty much ends up on sptr.net anyway.

After reviewing a number of options, most of which involve handing my content and subscriber details to a corporation of some kind, I resolved to host an email service on my own server using the free and open source listmonk. That way, it is secure, and when I go, it goes. No eternal presence to be gleefully exploited to make wealthy people wealthier. I am getting quite tired of that. Anyway, here’s what I did.

Export subscriber details from SubStack

This step was easy enough and does not need explaining except to say when you read the csv download, you realise just how much metrics and profiling information becomes attached to a subscriber’s name. This is what you get:

Email,Name,Stripe plan,Cancel date,Start date,Paid upgrade date,Bestseller,Emails received (6mo),Emails dropped (6mo),num_emails_opened,Emails opened (6mo),Emails opened (7d),Emails opened (30d),Last email open,Links clicked,Last clicked at,Unique emails seen (6mo),Unique emails seen (7d),Unique emails seen (30d),Post views,Post views (7d),Post views (30d),Unique posts seen,Unique posts seen (7d),Unique posts seen (30d),Comments,Comments (7d),Comments (30d),Shares,Shares (7d),Shares (30d),Subscriptions gifted,First paid date,Revenue,Subscription source (free),Subscription source (paid),Days active (30d),Activity,Country,State/Province,Expiration date,Type,Sections
example@example.com,Fred Bloggs,,,2024-03-24T21:24:16.280Z,,,0,0,0,0,0,0,,0,,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,,$0.00,substack-notes,,0,0,GB,,,Free,"The NixImagery SubStack,"

Prepare subscriber import file

You don’t need all of that information. Edit the csv down to this:

email,name,attributes
example@example.com,Fred Bloggs,"{""country"":""GB""}"

The attributes are a JSON map containing arbitrary information about a user if you wish to use it. See the documentation. You need to pay attention to the quotes here, the JSON map is a string containing strings.

Install listmonk

I installed this on a Debian Beelink box that runs behind my router at home. The documentation suggests Docker to containerise the installation but I don’t like to load a lightweight server, so I installed the binary and its only real dependency, which is the postgres database software. I don’t need Docker just for this.

# update your system first
sudo apt update && sudo apt upgrade -y
# install the database software
sudo apt install -y postgresql postgresql-contrib wget nano
# configure the database
sudo -u postgres psql

Set up the database and permissions:

CREATE DATABASE listmonk;
CREATE USER listmonk WITH ENCRYPTED PASSWORD 'your_strong_db_password_here';
GRANT ALL PRIVILEGES ON DATABASE listmonk TO listmonk;
\q

I found there were permissions errors at this point, which required logging back in to postgres and explicitly connecting to the listmonk database to correct:

sudo -u postgres psql listmonk
ALTER SCHEMA public OWNER TO listmonk;
GRANT ALL ON SCHEMA public TO listmonk;
GRANT ALL PRIVILEGES ON DATABASE listmonk TO listmonk;
sudo systemctl restart postgresql

Now we can download the binary and checksum, check they are OK before installing. I am using 5.1.0 but check for later versions:

# download
wget https://github.com/knadh/listmonk/releases/download/v5.1.0/listmonk_5.1.0_linux_amd64.tar.gz
wget https://github.com/knadh/listmonk/releases/download/v5.1.0/listmonk_5.1.0_checksums.txt
# check
sha256sum -c listmonk_5.1.0_checksums.txt
# unpack and install (you might want to do this in a separate directory - I didn't)
tar -xvf listmonk_5.1.0_linux_amd64.tar.gz
sudo install -v listmonk /usr/local/bin/

Configuration

You need to have listmonk create a new configuration file which you can edit:

listmonk --new-config
nano config.toml

The config file:

[app]
address = "0.0.0.0:9000"
admin_username = "admin"
admin_password = "your_admin_password"

[db]
host = "localhost"
port = 5432
user = "listmonk"
password = "your_strong_db_password_here"
database = "listmonk"
ssl_mode = "disable"

Now you’re ready to initialise the database schema and run the service:

listmonk --install
listmonk

Ready to play

You can now visit your listmonk web user interface at http://your_server_ip_address:9000, using the admin details you put in the config file.

To make listmonk run as a service and start automatically after a reboot, then create a service file at /etc/systemd/system/listmonk.service as follows:

[Unit]
Description=Listmonk Newsletter Manager
After=network.target

[Service]
ExecStart=/usr/local/bin/listmonk
WorkingDirectory=/home/your_user/
Restart=always
User=your_user
Group=your_user
Environment=LISTMONK_CONFIG=/path/to/config.toml

[Install]
WantedBy=multi-user.target
# enable the service
sudo systemctl daemon-reload
sudo systemctl enable listmonk
sudo systemctl start listmonk

Going outdoors

I wanted the interface to be visible on the web, so I had to open up port 9000 in the local router and point list.sptr.net at it in the DNS. Because the server already runs Apache as a web server, I created an additional virtual host to act as a reverse proxy.

sudo a2enmod proxy proxy_http
sudo systemctl restart apache2
<VirtualHost *:443>
    ServerName list.sptr.net

    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass / http://127.0.0.1:9000/
    ProxyPassReverse / http://127.0.0.1:9000/

    ErrorLog ${APACHE_LOG_DIR}/listmonk_error.log
    CustomLog ${APACHE_LOG_DIR}/listmonk_access.log combined
</VirtualHost>

<VirtualHost *:80>
    ServerName list.sptr.net
    Redirect permanent / https://list.sptr.net/
</VirtualHost>

Don’t forget to run certbot so you can connect securely over ssl. For media/image uploads, you need to make sure your listmonk upload folder is readable and publicly visible. I created a new one:

sudo mkdir /opt/mystuff
sudo mkdir /opt/mystuff/uploads
sudo chown -R youruser:yourgroup /opt/mystuff/uploads/
sudo systemctl restart listmonk

Up and running

Obviously, testing is crucial to save yourself from embarrassment with your subscribers, so check and test everything works as you want it to by using a few test lists, campaigns and email accounts. Follow the documentation – the subscriber fields are really useful.

Once that’s done, you have a fully operational newsletter service, free of corporate nonsense and free of charge. Gotta love open source.

  • the NixImagery Post / Fugacious Ideas

  • © Copyright Nick Hood