Listmonk: Setup & Known Issues
Listmonk is a powerful self-hosted newsletter server. Deploying it via Coolify is straightforward, but a few things will catch you off guard.
API credentials — admin login does NOT work for REST API (v4+)
In Listmonk v4+, the admin UI credentials are separate from API auth. You must create a dedicated API user:
Settings → Users → New → API tab
This gives you a token to use as Bearer auth. Admin username/password return 403.
Opt-in confirmation email — customizing it
The opt-in email template is embedded in the binary, not on disk. To customize it, you must:
- Download the static files from the Listmonk GitHub release tarball
- Mount a custom static dir into the container
- Add
--static-dir=/listmonk/staticto the container command
In docker-compose.yml:
command: './listmonk --static-dir=/listmonk/static'
volumes:
- '/data/coolify/listmonk-static/static:/listmonk/static:ro'
Template variable is {{ .OptinURL }} (with dot prefix).
⚠️ If you do this, do not redeploy Listmonk through the Coolify UI. Coolify will regenerate the compose file and overwrite your changes. To restart, SSH in and run:
cd /data/coolify/services/<SERVICE_UUID>/ docker compose up -d listmonk
Root URL and From address must be set manually
After first deploy, go to Settings → General:
- Root URL: must be
https://listmonk.yourdomain.com(notlocalhost:9000) - From address:
Your Name <email@yourdomain.com>
Without this, opt-in emails won’t send and confirmation links will be broken.
SMTP test button is buggy
The built-in SMTP test button in Settings often fails even when SMTP is working fine. Test by sending a real campaign instead.
Bilingual campaigns
Store subscriber language as an attribute at signup time via the API:
{"attribs": {"lang": "de"}}
Use in templates:
{{ if eq .Subscriber.Attribs.lang "de" }}German content{{ else }}English content{{ end }}
Postgres healthcheck — variable substitution issue
If the healthcheck uses ${POSTGRES_DB}, it may fail because the variable is substituted as empty at the host level before being passed to Docker.
Safe version:
healthcheck:
test: ["CMD-SHELL", "pg_isready"]