fix(notifications): deliver the authored body_html #475

Merged
Claude-Run merged 1 commit from fix/PMS-700-dispatch-body-html into main 2026-08-01 05:24:18 +02:00
Member

dispatch selected notification_templates.body_html and never read it, and the worker's deliver only ever called the plain-text send, so every dispatcher-delivered email went out single-part plain text and each seeded body_html was dead weight. The same two messages existed a second time as hard-coded Mailer::send_password_reset / Mailer::send_welcome bodies that DID send multipart/alternative, so which document a recipient got depended on whether the dispatcher was wired.

Mailer now has one send primitive, send_multipart(to, subject, text, html), with send_text as a plain-text wrapper over it; SmtpMailer builds the message through a free build_message helper so the shape is unit-testable without a transport. The two hard-coded template copies are gone. Migration 097 adds notifications.body_html; dispatch renders the template's HTML into it alongside the text (inside the same resolution pass, so the two parts cannot disagree) and the worker reads it back and hands it to send_multipart.

The portal contact setup mail was the third path: it called send_welcome directly, so it got HTML but no retries. It now dispatches auth.welcome like the staff welcome mail, which makes the dispatcher the only delivery path for both. Because there is no direct-send fallback left, the auth.password_reset / auth.welcome rows have to exist everywhere: migration 097 backfills them into existing tenants and copy_default_config copies them for new ones (they were default-tenant only before).

scripts/check-no-duplicate-mail-copy.nu fails the build if a seeded template's wording reappears under src/, so the duplicate copy cannot come back silently. Wired into just check and check.yml.

#PMS-700

`dispatch` selected `notification_templates.body_html` and never read it, and the worker's `deliver` only ever called the plain-text send, so every dispatcher-delivered email went out single-part plain text and each seeded `body_html` was dead weight. The same two messages existed a second time as hard-coded `Mailer::send_password_reset` / `Mailer::send_welcome` bodies that DID send `multipart/alternative`, so which document a recipient got depended on whether the dispatcher was wired. `Mailer` now has one send primitive, `send_multipart(to, subject, text, html)`, with `send_text` as a plain-text wrapper over it; `SmtpMailer` builds the message through a free `build_message` helper so the shape is unit-testable without a transport. The two hard-coded template copies are gone. Migration 097 adds `notifications.body_html`; `dispatch` renders the template's HTML into it alongside the text (inside the same resolution pass, so the two parts cannot disagree) and the worker reads it back and hands it to `send_multipart`. The portal contact setup mail was the third path: it called `send_welcome` directly, so it got HTML but no retries. It now dispatches `auth.welcome` like the staff welcome mail, which makes the dispatcher the only delivery path for both. Because there is no direct-send fallback left, the `auth.password_reset` / `auth.welcome` rows have to exist everywhere: migration 097 backfills them into existing tenants and `copy_default_config` copies them for new ones (they were default-tenant only before). `scripts/check-no-duplicate-mail-copy.nu` fails the build if a seeded template's wording reappears under `src/`, so the duplicate copy cannot come back silently. Wired into `just check` and check.yml. #PMS-700
fix(notifications): deliver the authored body_html
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 1m19s
Check / fmt + clippy + build + tests (pull_request) Successful in 3m49s
Integration / integration tests (pull_request) Successful in 12m45s
Create release / Gate (release-branch merges only) (pull_request) Successful in 3s
Create release / Create release from merged PR (pull_request) Has been skipped
f1a45c9640
`dispatch` selected `notification_templates.body_html` and never read it, and the worker's `deliver` only ever called the plain-text send, so every dispatcher-delivered email went out single-part plain text and each seeded `body_html` was dead weight. The same two messages existed a second time as hard-coded `Mailer::send_password_reset` / `Mailer::send_welcome` bodies that DID send `multipart/alternative`, so which document a recipient got depended on whether the dispatcher was wired.

`Mailer` now has one send primitive, `send_multipart(to, subject, text, html)`, with `send_text` as a plain-text wrapper over it; `SmtpMailer` builds the message through a free `build_message` helper so the shape is unit-testable without a transport. The two hard-coded template copies are gone. Migration 097 adds `notifications.body_html`; `dispatch` renders the template's HTML into it alongside the text (inside the same resolution pass, so the two parts cannot disagree) and the worker reads it back and hands it to `send_multipart`.

The portal contact setup mail was the third path: it called `send_welcome` directly, so it got HTML but no retries. It now dispatches `auth.welcome` like the staff welcome mail, which makes the dispatcher the only delivery path for both. Because there is no direct-send fallback left, the `auth.password_reset` / `auth.welcome` rows have to exist everywhere: migration 097 backfills them into existing tenants and `copy_default_config` copies them for new ones (they were default-tenant only before).

`scripts/check-no-duplicate-mail-copy.nu` fails the build if a seeded template's wording reappears under `src/`, so the duplicate copy cannot come back silently. Wired into `just check` and check.yml.

#PMS-700
Claude-Run deleted branch fix/PMS-700-dispatch-body-html 2026-08-01 05:24:19 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!475
No description provided.