Files
claudeandClaude Opus 5.5 a74d580b0a Style newsletters and system e-mails like www.eom.dev
Catppuccin Mocha campaign template (default) and archive variant, kept
in sync in the database; system e-mail header/footer overridden via
--static-dir from a ConfigMap. PNG logo for mail clients. Tested in
Mailpit (desktop, phone width; 87% CSS support, rest inlined).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 21:36:39 -05:00

5.2 KiB

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 (listmonk static client) with the same "eom.dev account" button as the other services. Only e-mail addresses listed in listmonk_oidc_admins can sign in, so being a member is not enough to get into the admin UI. The admin login page sends you straight to Dex. For the admin password login, kept as a fallback for when Dex is down, use /admin/login?local=1.
  • Mail goes out through postfix.eom.dev:587 as the LDAP account news, 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-issuer TLS, nfs-client volumes, 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

  1. In LDAP, create the posix account news with mail: news@eom.dev and add it to cn=Postfix Users,ou=Postfix,ou=Services,dc=eom,dc=dev. Its password is listmonk_smtp_password.
  2. Add the listmonk client to Dex (ericomeehan.dex/playbooks/deploy-eom.yml) and run that play.

E-mail design

Newsletters and listmonk's system e-mails (opt-in confirmation, password reset and so on) use the Catppuccin Mocha palette and the eom.dev logo, matching www.eom.dev.

  • templates/email/campaign.html.j2 produces the eom.dev campaign template, which is the default for new campaigns, and an eom.dev archive variant without unsubscribe links. Both are kept in sync in the database on every run.
  • templates/email/base.html.j2 overrides only the shared system e-mail header and footer. It is mounted from a ConfigMap through --static-dir.
  • The logo is a PNG (www.eom.dev/static/email-logo.png), because most mail clients don't show SVG.
  • Set listmonk_email_postal_address to add a mailing address to every footer. US anti-spam law (CAN-SPAM) requires one in commercial e-mail.

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