Review: Admin blast endpoint /email/blast
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 directoryservices/exists with 9 existing modules. Correct. - [x]
src/basketball_api/routes/admin.py— verified exists. Hasrequire_admindependency (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: hasload_email_template()(line 1107) which blast endpoint will call. Correct boundary. - [x]
contract_tokencolumn — verified on Player model (models.pyline 232), unique constraint exists - [x]
contract_signed_atcolumn — verified on Player model (models.pyline 229) - [x]
EmailLogmodel — 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 functionsend_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-reminderand/email/jersey-reminderendpoints 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_contractsquery 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 notearch-emailfor 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 ablocked-by:751,750label (or equivalent) to make the dependency chain machine-readable on the board.