Review: Admin blast endpoint /email/blast

review-752-2026-04-03 Doc

review needs-refinement

Verdict: NEEDS_REFINEMENT

Template Completeness

  • [x] Type — Feature
  • [x] Lineage — depends on MJML system + send_templated_email() tickets; supersedes board item #724
  • [x] Repo — forgejo_admin/basketball-api
  • [x] User Story — clear admin-wants-blast-endpoint story
  • [x] Context — explains current hardcoded pattern, decision to genericize, query registry design
  • [x] File Targets — specific create/modify/do-not-touch paths
  • [x] Acceptance Criteria — 7 testable conditions
  • [x] Test Expectations — 5 unit tests with run command
  • [x] Constraints — query return shape, placeholder format, auth dependency, pattern matching
  • [x] Checklist — PR opened, tests pass, no unrelated changes
  • [x] Related — project link, superseded ticket, dependency tickets

Traceability

  • [x] story:WS-S7 label — "As an admin, I want to send branded email announcements so that parent comms are professional and consistent"
  • [x] story:WS-S7 note verified — found in project-westside-basketball user-stories section (Admin stories)
  • [x] story:WS-S22 label — "As a parent, I want to receive clear email communications with action links so that I never miss a deadline"
  • [x] story:WS-S22 note verified — found in project-westside-basketball user-stories section (Parent stories)
  • [x] arch:email label — component identified
  • [ ] arch note MISSING — [SCOPE] Create architecture note arch-email for the email subsystem. Should cover: Gmail OAuth, send functions, MJML templates, query registry, EmailLog model.
  • [x] Forgejo issue — forgejo_admin/basketball-api#295, open

File Targets

  • [x] src/basketball_api/services/email_queries.py — to create. Does not exist yet. Parent directory services/ exists with 9 existing modules. Correct.
  • [x] src/basketball_api/routes/admin.py — verified exists. Has require_admin dependency (line 48), existing email endpoints at /email/profile-reminder (line 438) and /email/jersey-reminder (line 847). Good placement for new endpoint.
  • [x] src/basketball_api/services/email.py — listed as NOT to touch. Confirmed: has load_email_template() (line 1107) which blast endpoint will call. Correct boundary.
  • [x] contract_token column — verified on Player model (models.py line 232), unique constraint exists
  • [x] contract_signed_at column — verified on Player model (models.py line 229)
  • [x] EmailLog model — verified exists in models.py, used across 6 files including test_admin_email.py
Targets are specific enough for an agent to act without guessing.

Repo Placement

Correct. Issue filed on forgejo_admin/basketball-api, all file targets are in that repo. Single-repo scope. No cross-repo work needed.

Dependencies

  • [ ] Board item #751 (Issue #294: Generic send_templated_email() + EmailType migration) — PENDING, in backlog. HARD BLOCKER. The function send_templated_email() does not exist in the codebase. This ticket cannot be implemented until #751 is merged.
  • [ ] Board item #750 (Issue #293: MJML email system — brand base + three layouts + docker build) — PENDING, in backlog. HARD BLOCKER. The blast endpoint accepts a "layout" param that references the MJML layout system from #750's scope.
  • [x] Board item #724 (Contract reminder email endpoint) — in needs_approval, superseded by this ticket per Lineage. No conflict.

Acceptance Criteria

7 acceptance criteria evaluated:
  • [x] AC 1 (POST /email/blast accepts JSON body) — testable via HTTP POST assertion
  • [x] AC 2 (unsigned_contracts query logic) — testable via DB fixture + query assertion
  • [x] AC 3 (per-recipient placeholder merge) — testable via mock/assertion on rendered output
  • [x] AC 4 (test_email param limits send) — testable, existing pattern proven in profile-reminder endpoint
  • [x] AC 5 (response format) — testable via JSON schema assertion
  • [x] AC 6 (EmailLog entry) — testable via DB query after send
  • [x] AC 7 (admin auth) — testable via 401 check, existing pattern in test_admin_email.py
All criteria are specific and programmatically verifiable. Test run command is concrete: pytest tests/test_admin_email.py -v.

Blast Radius

  • Existing /email/profile-reminder and /email/jersey-reminder endpoints explicitly excluded — no regression risk.
  • load_email_template() is a pure function (reads file, substitutes vars, returns string) — safe to call from new endpoint.
  • No downstream consumers beyond admin UI. The query registry pattern is new and self-contained.
  • The unsigned_contracts query touches Player model fields well-established across 7 migrations and extensive test_contract.py coverage.
  • Rollback is straightforward — single endpoint addition, no schema changes.

Decomposition Assessment

Apply the three-thing limit and five-minute rule:
  • 2 file targets (1 create, 1 modify) across 1 repo — under the 3-file threshold
  • 7 acceptance criteria — exceeds the 5-AC guideline, but all target a single endpoint with one query implementation. Tightly coupled; splitting would create artificial boundaries.
  • No independent subtasks that could be parallelized — the query registry and endpoint are co-dependent.
  • Estimated agent time: 3-4 minutes — under the 5-minute limit.
No decomposition needed.

Recommendation

  • [SCOPE] Create architecture note arch-email for the email subsystem in pal-e-docs. Should cover: Gmail OAuth flow, send functions, MJML templates, query registry, EmailLog model.
  • [BODY] Add explicit blocker note in issue body: "BLOCKED BY: #293 (MJML system) and #294 (send_templated_email). Do not move to next_up until both dependencies are merged." The Lineage section mentions dependencies but does not clearly mark them as execution blockers.
  • [LABEL] Consider adding a blocked-by:751,750 label (or equivalent) to make the dependency chain machine-readable on the board.