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

86 lines
5.2 KiB
Markdown

# ansible-role-listmonk
Deploys [listmonk](https://listmonk.app) 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:
```html
<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
```sh
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