Email template deployment is fragmented — need single source of truth with CI #12

Open
opened 2026-04-08 22:29:39 +00:00 by forgejo_admin · 0 comments
Contributor

Type

Bug

Lineage

Discovered during jersey reminder email work (2026-04-04 through 2026-04-08). Multiple rounds of broken emails caused by fragmented template system — HTML template, plain text body, and deployment path all diverged.

Repo

forgejo_admin/westside-emails and forgejo_admin/basketball-api

What Broke

The email template system has three independent sources of content that must be manually kept in sync:

  1. MJML source in westside-emails/src/ — the canonical template
  2. Compiled HTML deployed via hostPath (~/westside-email-templates/compiled/) AND baked into the Docker image (COPY --from=email-compiler) — two copies that can diverge
  3. Hardcoded plain text body in basketball_api/services/email.py — completely separate from the template, manually maintained

This caused: stale "three options" and "March 30 deadline" in plain text while HTML was correct, wrong jersey images, opt-out showing after removal — each fix only addressed one layer, missing the others.

Repro Steps

  1. Update MJML template in westside-emails
  2. Manually compile and copy to hostPath
  3. Forget to update the hardcoded plain text in email.py
  4. Parents see conflicting content depending on which version their email client renders

Expected Behavior

Single source of truth:

  1. MJML source in westside-emails is the only place content is authored
  2. CI pipeline in westside-emails compiles MJML to HTML on merge
  3. basketball-api consumes the compiled HTML from one location (not two)
  4. Plain text is auto-generated from HTML using the existing _html_to_plain_text() function in email.py — not hardcoded
  5. No manual hostPath copying

Fix Required

1. Add Woodpecker CI to westside-emails — on merge to main: compile all MJML templates, push compiled HTML to a known location (Harbor artifact, MinIO, or git subtree that basketball-api pulls)

2. Remove hostPath mount — basketball-api should get templates from one source: either baked into the Docker image (via the email-compiler build stage) or pulled from MinIO/artifact store at startup

3. Use _html_to_plain_text() for jersey-remindersend_jersey_reminder_email in email.py should call _html_to_plain_text(html_body) instead of maintaining a separate hardcoded plain text body. Other email functions already do this (see line 1193).

4. Remove hardcoded plain text body from send_jersey_reminder_email — once auto-generation is in place

Environment

  • westside-emails: no CI, manual compilation
  • basketball-api Dockerfile: COPY --from=email-compiler bakes templates at build time
  • basketball-api k8s: hostPath ~/westside-email-templates/compiled/ overlays the baked templates
  • Result: three possible template versions at any time

Acceptance Criteria

  • westside-emails has Woodpecker CI that compiles MJML on merge
  • basketball-api uses ONE template source (not hostPath + Docker image)
  • send_jersey_reminder_email uses _html_to_plain_text() instead of hardcoded plain text
  • Changing a template in westside-emails automatically propagates to basketball-api without manual steps
  • No manual copying to hostPath required
  • project-westside-basketball
  • story:WS-S22 — parent email communications
  • forgejo_admin/westside-emails#10 — division-aware images (exposed this problem)
  • Key files: westside-emails/.woodpecker.yaml (to create), basketball-api/Dockerfile (email-compiler stage), basketball-api/services/email.py:1283 (hardcoded plain text)
### Type Bug ### Lineage Discovered during jersey reminder email work (2026-04-04 through 2026-04-08). Multiple rounds of broken emails caused by fragmented template system — HTML template, plain text body, and deployment path all diverged. ### Repo `forgejo_admin/westside-emails` and `forgejo_admin/basketball-api` ### What Broke The email template system has three independent sources of content that must be manually kept in sync: 1. **MJML source** in `westside-emails/src/` — the canonical template 2. **Compiled HTML** deployed via hostPath (`~/westside-email-templates/compiled/`) AND baked into the Docker image (`COPY --from=email-compiler`) — two copies that can diverge 3. **Hardcoded plain text body** in `basketball_api/services/email.py` — completely separate from the template, manually maintained This caused: stale "three options" and "March 30 deadline" in plain text while HTML was correct, wrong jersey images, opt-out showing after removal — each fix only addressed one layer, missing the others. ### Repro Steps 1. Update MJML template in westside-emails 2. Manually compile and copy to hostPath 3. Forget to update the hardcoded plain text in email.py 4. Parents see conflicting content depending on which version their email client renders ### Expected Behavior Single source of truth: 1. **MJML source** in westside-emails is the only place content is authored 2. **CI pipeline** in westside-emails compiles MJML to HTML on merge 3. **basketball-api** consumes the compiled HTML from one location (not two) 4. **Plain text** is auto-generated from HTML using the existing `_html_to_plain_text()` function in email.py — not hardcoded 5. No manual hostPath copying ### Fix Required **1. Add Woodpecker CI to westside-emails** — on merge to main: compile all MJML templates, push compiled HTML to a known location (Harbor artifact, MinIO, or git subtree that basketball-api pulls) **2. Remove hostPath mount** — basketball-api should get templates from one source: either baked into the Docker image (via the email-compiler build stage) or pulled from MinIO/artifact store at startup **3. Use `_html_to_plain_text()` for jersey-reminder** — `send_jersey_reminder_email` in email.py should call `_html_to_plain_text(html_body)` instead of maintaining a separate hardcoded plain text body. Other email functions already do this (see line 1193). **4. Remove hardcoded plain text body** from `send_jersey_reminder_email` — once auto-generation is in place ### Environment - westside-emails: no CI, manual compilation - basketball-api Dockerfile: `COPY --from=email-compiler` bakes templates at build time - basketball-api k8s: hostPath `~/westside-email-templates/compiled/` overlays the baked templates - Result: three possible template versions at any time ### Acceptance Criteria - [ ] westside-emails has Woodpecker CI that compiles MJML on merge - [ ] basketball-api uses ONE template source (not hostPath + Docker image) - [ ] `send_jersey_reminder_email` uses `_html_to_plain_text()` instead of hardcoded plain text - [ ] Changing a template in westside-emails automatically propagates to basketball-api without manual steps - [ ] No manual copying to hostPath required ### Related - `project-westside-basketball` - `story:WS-S22` — parent email communications - `forgejo_admin/westside-emails#10` — division-aware images (exposed this problem) - Key files: `westside-emails/.woodpecker.yaml` (to create), `basketball-api/Dockerfile` (email-compiler stage), `basketball-api/services/email.py:1283` (hardcoded plain text)
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
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
ldraney/westside-emails#12
No description provided.