Token is generated once and kept in Secret listmonk-newsletter-api. Tested: create, test-send and schedule work; subscribers/settings 403. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ansible-role-listmonk
Deploys listmonk v6.2.0, the eom.dev newsletter, to Kubernetes.
It fits in with the rest of eom.dev:
- Login goes through Dex (
listmonkstatic client) with the same "eom.dev account" button as the other services. Only e-mail addresses listed inlistmonk_oidc_adminscan sign in, so being a member is not enough to get into the admin UI. A password login for theadminuser is kept as a fallback. - Mail goes out through
postfix.eom.dev:587as the LDAP accountnews, the same way Gitea, Nextcloud and PeerTube send mail. - Look: the public pages (subscribe, archive, preferences, unsubscribe) use the Catppuccin Mocha/Latte palette from
dex.eom.dev, along with the eom.dev logo and favicon. - Cluster conventions:
ca-issuerTLS,nfs-clientvolumes, and anti-affinity that keeps the pods off the ingress-nginx node.
What it creates
| Object | Purpose |
|---|---|
Secret/listmonk |
DB password and bootstrap admin credentials |
StatefulSet/listmonk-db |
PostgreSQL 17 (alpine, uid 70) on nfs-client |
Deployment/listmonk |
Runs --install --idempotent, then --upgrade, then serves on :9000 |
PVC/listmonk-uploads |
Media uploaded for campaigns |
Ingress/listmonk |
https://listmonk.eom.dev |
listmonk keeps most of its configuration in the settings table, not in a config file. The role writes those settings with an idempotent SQL script (templates/configure.sql.j2) that it runs in the database pod. The script covers SMTP, Dex, theme, captcha, privacy, the Dex admin users and the lists in listmonk_lists. On the first run it also removes listmonk's sample data. listmonk is restarted only when a row actually changes.
Settings the role manages are reset on every run. Change them in defaults/main.yml or listmonk_settings_extra, not in the UI. Everything else, including templates, campaigns and subscribers, stays under the UI's control.
Required variables (Vault)
| Variable | Notes |
|---|---|
listmonk_db_password |
16+ chars. Only used when the volume is first initialised |
listmonk_admin_password |
8+ chars, for the admin fallback login |
listmonk_smtp_password |
Password of the news LDAP account |
listmonk_oidc_client_secret |
Must match the listmonk client in Dex |
The eom.dev playbook maps them from the Vault names listmonk_postgresql_password, listmonk_admin_password, listmonk_smtp_password and listmonk_dex_client_secret.
One-time setup outside this role
- In LDAP, create the posix account
newswithmail: news@eom.devand add it tocn=Postfix Users,ou=Postfix,ou=Services,dc=eom,dc=dev. Its password islistmonk_smtp_password. - Add the
listmonkclient to Dex (ericomeehan.dex/playbooks/deploy-eom.yml) and run that play.
API users
| User | Can do | Token |
|---|---|---|
membership |
Read and manage subscribers on eom.dev (signup opt-in, profile toggle) |
listmonk_api_token (Vault); same value as LISTMONK_API_TOKEN in the www membership Secret |
newsletter |
Create, edit, test-send and schedule campaigns to eom.dev; no subscriber data or settings |
Generated by the role, stored in Secret/listmonk-newsletter-api (url, username, token) |
Neither user can read settings or other lists. To rotate the newsletter token, delete its Secret and run the play again.
Embedding a signup form
listmonk accepts a plain HTML POST, so any eom.dev page can include a form:
<form method="post" action="https://listmonk.eom.dev/subscription/form">
<input type="hidden" name="nonce">
<input type="email" name="email" required placeholder="you@example.com">
<input type="hidden" name="l" value="LIST-UUID">
<button type="submit">Subscribe</button>
</form>
Copy the list UUID from Lists → eom.dev → Forms in the admin UI. The form uses double opt-in, so subscribers have to confirm by e-mail. The page at https://listmonk.eom.dev/subscription/form adds an Altcha proof-of-work captcha.
Testing
ansible-playbook -i tests/inventory tests/render.yml # renders into .render/
This version was tested against listmonk/listmonk:v6.2.0 and postgres:17.11-alpine. The SQL changes 12 settings, 1 user, 1 list and the sample data on the first run, and nothing on the second.
License
MIT-0