Fix confirmation email: use MJML template, accept team preference, update Stripe label #383

Closed
opened 2026-04-07 19:10:07 +00:00 by forgejo_admin · 1 comment

Type

Feature

Lineage

Standalone — discovered during registration flow audit (2026-04-07). Confirmation email hardcodes "March 24 tryout" details. Stripe product name says "Tryout Registration."

Repo

forgejo_admin/basketball-api

User Story

As a parent who just paid for registration
I want the confirmation email to accurately confirm my registration with my credentials and a profile link
So that I'm not told to attend a tryout that already happened

Context

send_confirmation_email() in email.py:65-170 builds inline HTML via _build_confirmation_html() (lines 200-384) with hardcoded "Tuesday, March 24" tryout details at Kongo Gym. The load_email_template() function (line 1107) already exists and is used by jersey-reminder — it loads compiled HTML from /data/email-templates/ and does {{placeholder}} string replacement. The MJML template is being created in a parallel ticket in westside-email-templates. Stripe Checkout Session product name is built inline at register.py:1346 as "Tryout Registration." The frontend is sending a new optional team_preference field in a parallel ticket.

File Targets

Files to modify:

  • src/basketball_api/services/email.py
    • send_confirmation_email() (line 65): swap to load_email_template("registration-confirmation", data)
    • Line 94: update subject to f"{player.name}'s Registration Confirmed — {tenant.name}"
    • Lines 95-122: rewrite plaintext fallback (remove tryout details)
    • Lines 200-384: DELETE _build_confirmation_html() entirely
  • src/basketball_api/routes/register.py
    • Line 1346: change type_label from "Tryout" to "Registration" for Stripe product name
    • Registration request body schema: add optional team_preference: str | None

Files NOT to touch:

  • src/basketball_api/routes/register.py GET handler (server-rendered form) — works fine
  • src/basketball_api/services/email.py other email functions — separate scope
  • src/basketball_api/brand.py — still used by server-rendered pages

Acceptance Criteria

  • When a card payment completes, email subject says "Registration Confirmed"
  • When a card payment completes, email body has no hardcoded event dates
  • When a card payment completes, email uses MJML template via load_email_template()
  • When a promo registration completes, same correct email is sent
  • When MJML template is missing, plaintext fallback sends successfully
  • When I POST with team_preference, it's stored on the Player record
  • When I POST without team_preference, registration works normally
  • Stripe checkout page shows "Registration" not "Tryout Registration"
  • Email ?token= link still works for profile management
  • _build_confirmation_html() no longer exists in codebase

Test Expectations

  • Unit test: send_confirmation_email() with mock template file — verify placeholder replacement
  • Unit test: send_confirmation_email() without template file — verify plaintext fallback
  • Integration test: POST /api/register with team_preference field — verify stored on Player
  • Integration test: POST /api/register without team_preference — verify no error
  • Run command: pytest tests/ -k "test_register or test_email"

Constraints

  • load_email_template() reads from settings.email_templates_dir (default /data/email-templates)
  • Build credentials_block as conditional HTML snippet — empty string when no credentials
  • Player.team_preference is TeamPreference enum (local/travel/both) — if frontend sends team name string, either map it or include raw in admin notification email for now
  • Plaintext fallback must still work when template file doesn't exist (deploy ordering)

Checklist

  • PR opened
  • Tests pass
  • _build_confirmation_html() deleted
  • No unrelated changes
  • westside-basketball — project
  • forgejo_admin/westside-email-templates — provides the compiled MJML template (parallel ticket)
  • forgejo_admin/westside-app — sends team_preference in POST (parallel ticket)
  • Spec: pal-e-platform/docs/superpowers/specs/2026-04-07-registration-flow-fix-design.md
### Type Feature ### Lineage Standalone — discovered during registration flow audit (2026-04-07). Confirmation email hardcodes "March 24 tryout" details. Stripe product name says "Tryout Registration." ### Repo `forgejo_admin/basketball-api` ### User Story As a parent who just paid for registration I want the confirmation email to accurately confirm my registration with my credentials and a profile link So that I'm not told to attend a tryout that already happened ### Context `send_confirmation_email()` in `email.py:65-170` builds inline HTML via `_build_confirmation_html()` (lines 200-384) with hardcoded "Tuesday, March 24" tryout details at Kongo Gym. The `load_email_template()` function (line 1107) already exists and is used by jersey-reminder — it loads compiled HTML from `/data/email-templates/` and does `{{placeholder}}` string replacement. The MJML template is being created in a parallel ticket in `westside-email-templates`. Stripe Checkout Session product name is built inline at `register.py:1346` as "Tryout Registration." The frontend is sending a new optional `team_preference` field in a parallel ticket. ### File Targets Files to modify: - `src/basketball_api/services/email.py` - `send_confirmation_email()` (line 65): swap to `load_email_template("registration-confirmation", data)` - Line 94: update subject to `f"{player.name}'s Registration Confirmed — {tenant.name}"` - Lines 95-122: rewrite plaintext fallback (remove tryout details) - Lines 200-384: DELETE `_build_confirmation_html()` entirely - `src/basketball_api/routes/register.py` - Line 1346: change `type_label` from "Tryout" to "Registration" for Stripe product name - Registration request body schema: add optional `team_preference: str | None` Files NOT to touch: - `src/basketball_api/routes/register.py` GET handler (server-rendered form) — works fine - `src/basketball_api/services/email.py` other email functions — separate scope - `src/basketball_api/brand.py` — still used by server-rendered pages ### Acceptance Criteria - [ ] When a card payment completes, email subject says "Registration Confirmed" - [ ] When a card payment completes, email body has no hardcoded event dates - [ ] When a card payment completes, email uses MJML template via `load_email_template()` - [ ] When a promo registration completes, same correct email is sent - [ ] When MJML template is missing, plaintext fallback sends successfully - [ ] When I POST with team_preference, it's stored on the Player record - [ ] When I POST without team_preference, registration works normally - [ ] Stripe checkout page shows "Registration" not "Tryout Registration" - [ ] Email ?token= link still works for profile management - [ ] `_build_confirmation_html()` no longer exists in codebase ### Test Expectations - [ ] Unit test: `send_confirmation_email()` with mock template file — verify placeholder replacement - [ ] Unit test: `send_confirmation_email()` without template file — verify plaintext fallback - [ ] Integration test: POST `/api/register` with team_preference field — verify stored on Player - [ ] Integration test: POST `/api/register` without team_preference — verify no error - Run command: `pytest tests/ -k "test_register or test_email"` ### Constraints - `load_email_template()` reads from `settings.email_templates_dir` (default `/data/email-templates`) - Build `credentials_block` as conditional HTML snippet — empty string when no credentials - `Player.team_preference` is `TeamPreference` enum (local/travel/both) — if frontend sends team name string, either map it or include raw in admin notification email for now - Plaintext fallback must still work when template file doesn't exist (deploy ordering) ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] `_build_confirmation_html()` deleted - [ ] No unrelated changes ### Related - `westside-basketball` — project - `forgejo_admin/westside-email-templates` — provides the compiled MJML template (parallel ticket) - `forgejo_admin/westside-app` — sends team_preference in POST (parallel ticket) - Spec: `pal-e-platform/docs/superpowers/specs/2026-04-07-registration-flow-fix-design.md`
Author
Owner

APPROVED — file targets verified, load_email_template confirmed, no other callers of _build_confirmation_html, no conflicts.

Minor line range corrections (non-blocking):

  • _build_confirmation_html() ends at line 348, not 384. The function spans lines 200-348.
  • Plaintext body with hardcoded tryout details runs through line 129 (not 122). The tryout block is lines 117-122 but the full body string closes at 129.
  • APIRegistrationRequest schema is at line 1130 — team_preference field needs to be added there.

Verified:

  • send_confirmation_email() at line 65 ✓
  • Subject at line 94 ✓
  • _build_confirmation_html() at line 200 ✓
  • load_email_template() at line 1107 with {{placeholder}} replacement ✓
  • _build_confirmation_html only called from send_confirmation_email() (lines 132-133) ✓
  • Player.team_preference field exists as TeamPreference enum in models.py ✓
  • Stripe type_label at line 1346 ✓
  • Test files: test_register.py, test_promo_registration.py, test_admin_email.py all exist ✓
  • No unmerged branches touching email.py or register.py — no conflicts ✓
APPROVED — file targets verified, `load_email_template` confirmed, no other callers of `_build_confirmation_html`, no conflicts. **Minor line range corrections (non-blocking):** - `_build_confirmation_html()` ends at line 348, not 384. The function spans lines 200-348. - Plaintext body with hardcoded tryout details runs through line 129 (not 122). The tryout block is lines 117-122 but the full body string closes at 129. - `APIRegistrationRequest` schema is at line 1130 — `team_preference` field needs to be added there. **Verified:** - `send_confirmation_email()` at line 65 ✓ - Subject at line 94 ✓ - `_build_confirmation_html()` at line 200 ✓ - `load_email_template()` at line 1107 with `{{placeholder}}` replacement ✓ - `_build_confirmation_html` only called from `send_confirmation_email()` (lines 132-133) ✓ - `Player.team_preference` field exists as `TeamPreference` enum in models.py ✓ - Stripe `type_label` at line 1346 ✓ - Test files: `test_register.py`, `test_promo_registration.py`, `test_admin_email.py` all exist ✓ - No unmerged branches touching `email.py` or `register.py` — no conflicts ✓
Sign in to join this conversation.
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
forgejo_admin/basketball-api#383
No description provided.