Review: Generic send_templated_email() + EmailType migration
Verdict: NEEDS_REFINEMENT
Template Completeness
- [x] Type — Feature
- [x] Lineage — references MJML email system ticket dependency
- [x] Repo — forgejo_admin/basketball-api
- [x] User Story — clear developer-facing enabler story
- [x] Context — thorough explanation of current pattern and rationale for change
- [x] File Targets — 3 specific paths with "Files NOT to touch" section
- [x] Acceptance Criteria — 6 testable conditions
- [x] Test Expectations — 4 unit tests with run command
- [x] Constraints — function signature, OAuth, template rendering, plaintext fallback
- [x] Checklist — 4 items
- [x] Related — links to project page and MJML dependency
All required sections for Feature type are present and filled.
Traceability
- [x] story:WS-S7 — "As an admin, I want to send branded email announcements so that parent comms are professional and consistent" — verified in project-westside-basketball user-stories section (Admin list)
- [x] story:WS-S22 — "As a parent, I want to receive clear email communications with action links so that I never miss a deadline" — verified in project-westside-basketball user-stories section (Parent list)
- [x] arch:email — references email architecture component
- [ ] arch note MISSING — no
arch-emailnote found in pal-e-docs via search_notes. [SCOPE] Create architecture note arch-email for the email subsystem. - [x] Forgejo issue — forgejo_admin/basketball-api#294, state: open
File Targets
- [x]
src/basketball_api/services/email.py— verified exists. Contains 7 existing send functions (lines 65-1342).load_email_template()at line 1107 andget_gmail_client()at line 29 both confirmed present. Constraints references are accurate. - [x]
src/basketball_api/models.py— verified exists.EmailTypeenum at line 63 with 7 values (registration, admin_registration, reminder, roster_export, announcement, contract_signed, interest_notification). Nocontract_offerorcontract_reminderyet — ticket is accurate. - [x]
alembic/versions/— verified exists. 30 migrations present. Pattern for adding enum values established in027_add_email_type_enum_values.py(usesALTER TYPE emailtype ADD VALUE IF NOT EXISTS). Next migration would be 031.
All targets are specific enough for an agent to act on without guessing.
Repo Placement
OK. Issue filed on forgejo_admin/basketball-api, all file targets are in that repo. Single-repo change, no cross-repo scope.
Dependencies
- [x] Issue #293 / board item #750 (MJML email system) — status: pending (backlog, open). Lineage section references this.
send_templated_email()callsload_email_template()which reads compiled HTML from disk. Templates must exist for integration testing, but unit tests can mock the template loader. Soft dependency: not blocking code merge, but blocking production use. - [x] Issue #295 / board item #752 (Admin blast endpoint) — status: downstream consumer. Correctly identified as separate ticket in File Targets ("blast endpoint is a separate ticket").
- [x] Board item #724 (Contract reminder email endpoint) — in needs_approval column. Future consumer of
send_templated_email()+EmailType.contract_reminder.
No unresolved blocking dependencies. Execution order is correct.
Acceptance Criteria
- [x] AC1: Function signature is concrete with all parameters specified. Testable via unit test.
- [x] AC2: EmailLog fields are well-defined in the models (tenant_id, parent_id, player_id, email_type, recipient_email, gmail_message_id). Testable.
- [x] AC3: Plain text fallback algorithm specified in Constraints ("strip HTML tags, preserve link URLs"). Testable.
- [x] AC4: Enum values are specific strings. Testable via assertion on enum members.
- [x] AC5: Alembic up+down testable via
alembic upgrade/downgrade. Note: downgrade for enum values is a no-op per PostgreSQL limitation — consistent with migration 027 pattern. - [x] AC6: "Existing functions unchanged" — testable by running full test suite before and after.
All AC are testable and specific. No ambiguous language.
Blast Radius
- Additive only — no existing functions modified. Low blast radius.
- Alembic enum migration uses
ADD VALUE IF NOT EXISTS— safe, idempotent, proven pattern from migration 027. - No downstream consumers yet — blast endpoint (#295) and contract emails are separate tickets.
- Rollback: straightforward. New function is unused until a caller is added. Enum values cannot be removed from PostgreSQL but are harmless if unused.
Decomposition Assessment
Apply the three-thing limit and five-minute rule:
- 3 discrete changes: (1) new function in email.py, (2) enum values in models.py, (3) alembic migration. Exactly at the 3-thing limit.
- 6 AC + 4 test expectations. All in one repo, one cohesive feature.
- Estimated agent time: ~5 minutes. At the boundary but acceptable — the work is cohesive and cannot be meaningfully parallelized (the function depends on the enum values, tests depend on both).
No decomposition needed.
Recommendation
- [SCOPE] Create architecture note
arch-emailfor the email subsystem. This note should document: send functions, EmailType enum, EmailLog model, template loading viaload_email_template(), Gmail OAuth viaget_gmail_client(), and the MJML compiled template pipeline. Referenced by 8+ board items witharch:emaillabel — the backing note is overdue.