Gmail notification on jersey-public submission #431

Open
opened 2026-04-10 21:52:45 +00:00 by forgejo_admin · 1 comment

Type

Feature

Lineage

Depends on basketball-api#430 (POST endpoint). Part of System B production rollout. Architecture in arch-jersey-intake.

Repo

forgejo_admin/basketball-api

User Story

As Marcus
I want an email notification when a public jersey order lands
So that I know to review it promptly instead of polling the admin UI

Context

Per feedback_gmail_oauth_not_smtp.md and feedback_email_architecture.md, basketball-api already has a gmail-sdk integration for sending email from westsidebasketball@gmail.com. This ticket adds a FastAPI BackgroundTasks that fires on every successful POST.

Notifications go only to westsidebasketball@gmail.com. Do NOT cc Lucas, do NOT send to the submitter's email (no reply-to automation in this ticket).

File Targets

Files to modify:

  • src/basketball_api/routes/jersey_public.py — add BackgroundTasks dep, enqueue the email-send after successful insert
  • src/basketball_api/services/jersey_public_email.py (or matching existing email service location) — new helper that formats the email and calls the existing gmail sender

Files the agent should NOT touch:

  • Other email flows / templates
  • westside-emails repo — this notification is inline plain text, not MJML

Email content

Subject: New jersey order: <player_name> — <tier>

Body (plain text):

A new public jersey order was submitted.

Player: {player_name}
Email: {email}
Team: {team}
K/Q: {kq}
Top size: {top_size}
Short size: {short_size}
Tier: {tier}
Preferred numbers: {n1}, {n2}, {n3}  (blanks as —)

Submitter Keycloak sub: {submitter_keycloak_sub}
Submission ID: {id}
Submitted at: {created_at}

Review at: https://westsidekingsandqueens.tail5b443a.ts.net/admin/jersey-orders

The submitter_keycloak_sub line lets Marcus cross-reference in Keycloak Admin Console if there's any identity question.

Acceptance Criteria

  • After a successful POST, a Gmail email is enqueued via existing gmail-sdk
  • Uses FastAPI BackgroundTasks — POST response is not blocked by SMTP
  • Gmail send failure is logged but does NOT cause the POST to return 500
  • Subject and body match the template exactly
  • Tests mock the gmail sender — no real emails in CI

Test Expectations

  • Unit test: valid POST → email-send function called with correct subject + body
  • Unit test: mocked sender raises → POST still returns 201, error logged
  • Run command: pytest tests/ -k jersey_public_email

Constraints

  • Reuse existing gmail-sdk integration (feedback_gmail_oauth_not_smtp.md)
  • Do NOT send to submitter's email
  • Do NOT send receipt-style user-facing emails
  • Async via BackgroundTasks — never block POST
  • No real emails in tests

Checklist

  • PR opened against basketball-api main
  • Depends on #430 merged first
  • Tests pass with mocked sender
  • westside-basketball — project
  • story:WS-S31 — admin public jersey intake link
  • arch-jersey-intake — architecture doc
  • Depends on: basketball-api#430 (POST endpoint)
  • feedback_gmail_oauth_not_smtp.md — email architecture guidance
### Type Feature ### Lineage Depends on `basketball-api#430` (POST endpoint). Part of System B production rollout. Architecture in `arch-jersey-intake`. ### Repo `forgejo_admin/basketball-api` ### User Story As Marcus I want an email notification when a public jersey order lands So that I know to review it promptly instead of polling the admin UI ### Context Per `feedback_gmail_oauth_not_smtp.md` and `feedback_email_architecture.md`, basketball-api already has a gmail-sdk integration for sending email from `westsidebasketball@gmail.com`. This ticket adds a FastAPI `BackgroundTasks` that fires on every successful POST. Notifications go only to `westsidebasketball@gmail.com`. Do NOT cc Lucas, do NOT send to the submitter's email (no reply-to automation in this ticket). ### File Targets Files to modify: - `src/basketball_api/routes/jersey_public.py` — add `BackgroundTasks` dep, enqueue the email-send after successful insert - `src/basketball_api/services/jersey_public_email.py` (or matching existing email service location) — new helper that formats the email and calls the existing gmail sender Files the agent should NOT touch: - Other email flows / templates - `westside-emails` repo — this notification is inline plain text, not MJML ### Email content Subject: `New jersey order: <player_name> — <tier>` Body (plain text): ``` A new public jersey order was submitted. Player: {player_name} Email: {email} Team: {team} K/Q: {kq} Top size: {top_size} Short size: {short_size} Tier: {tier} Preferred numbers: {n1}, {n2}, {n3} (blanks as —) Submitter Keycloak sub: {submitter_keycloak_sub} Submission ID: {id} Submitted at: {created_at} Review at: https://westsidekingsandqueens.tail5b443a.ts.net/admin/jersey-orders ``` The `submitter_keycloak_sub` line lets Marcus cross-reference in Keycloak Admin Console if there's any identity question. ### Acceptance Criteria - [ ] After a successful POST, a Gmail email is enqueued via existing gmail-sdk - [ ] Uses FastAPI `BackgroundTasks` — POST response is not blocked by SMTP - [ ] Gmail send failure is logged but does NOT cause the POST to return 500 - [ ] Subject and body match the template exactly - [ ] Tests mock the gmail sender — no real emails in CI ### Test Expectations - [ ] Unit test: valid POST → email-send function called with correct subject + body - [ ] Unit test: mocked sender raises → POST still returns 201, error logged - [ ] Run command: `pytest tests/ -k jersey_public_email` ### Constraints - Reuse existing gmail-sdk integration (`feedback_gmail_oauth_not_smtp.md`) - Do NOT send to submitter's email - Do NOT send receipt-style user-facing emails - Async via `BackgroundTasks` — never block POST - No real emails in tests ### Checklist - [ ] PR opened against `basketball-api` main - [ ] Depends on #430 merged first - [ ] Tests pass with mocked sender ### Related - `westside-basketball` — project - `story:WS-S31` — admin public jersey intake link - `arch-jersey-intake` — architecture doc - Depends on: `basketball-api#430` (POST endpoint) - `feedback_gmail_oauth_not_smtp.md` — email architecture guidance
Author
Owner

Scope Review: APPROVED

Review note: review-949-2026-04-10

Scope is tight and well-constrained. All traceability legs verified (story:WS-S31 exists on project-westside-basketball, arch-jersey-intake note exists, depends-on #430 is open). Existing gmail_sdk.GmailClient integration confirmed at src/basketball_api/services/email.py with get_gmail_client(tenant, db) — reuse path is concrete. BackgroundTasks pattern already in use at routes/players.py:260 for reference.

File targets routes/jersey_public.py and services/jersey_public_email.py don't exist yet — correct: T3 (#430) creates the route, T4 modifies it. Do NOT dispatch until #430 merges.

No decomposition needed (2 files, 5 AC, ~5 min agent work).

Notes for dev agent:

  • Reuse services/email.py::get_gmail_client pattern, don't instantiate GmailClient directly
  • Mock at services.jersey_public_email module boundary, not the gmail_sdk library
  • Use logger.exception() inside the BackgroundTasks callable so tracebacks reach logs without propagating to the handler
## Scope Review: APPROVED Review note: `review-949-2026-04-10` Scope is tight and well-constrained. All traceability legs verified (story:WS-S31 exists on project-westside-basketball, arch-jersey-intake note exists, depends-on #430 is open). Existing `gmail_sdk.GmailClient` integration confirmed at `src/basketball_api/services/email.py` with `get_gmail_client(tenant, db)` — reuse path is concrete. `BackgroundTasks` pattern already in use at `routes/players.py:260` for reference. File targets `routes/jersey_public.py` and `services/jersey_public_email.py` don't exist yet — correct: T3 (#430) creates the route, T4 modifies it. Do NOT dispatch until #430 merges. No decomposition needed (2 files, 5 AC, ~5 min agent work). Notes for dev agent: - Reuse `services/email.py::get_gmail_client` pattern, don't instantiate `GmailClient` directly - Mock at `services.jersey_public_email` module boundary, not the `gmail_sdk` library - Use `logger.exception()` inside the BackgroundTasks callable so tracebacks reach logs without propagating to the handler
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#431
No description provided.