Validation: Issue #369 — send_first_payment_email + POST /admin/email/first-payment blast
Ticket
Forgejo: basketball-api#369 — board item #875
Shipped:
send_first_payment_email() in services/email.py builds branded HTML email with per-player prorated amount, Stripe checkout CTA, and recurring billing notice. POST /admin/email/first-payment blast endpoint sends to all signed players, supports test_email safety filter.Environment
Prod cluster, basketball-api namespace, gmail OAuth from
westsidebasktball@gmail.com (file-based token).Checks
| # | Criterion | How to Verify | Result | Evidence |
|---|---|---|---|---|
| 1 | Email function sends branded HTML with correct content | Multiple test sends to draneylucas@gmail.com before blast | PASS | Subject "Your First Monthly Payment | Westside Kings & Queens", prorated/full fees correct, CTA link working |
| 2 | EmailLog written with email_type=first_payment | Query EmailLog after each send | PASS | 32 EmailLog entries with first_payment type, unique gmail_message_ids, sent_at timestamps within 10 seconds of each other |
| 3 | Proration correct across all tiers ($200→$165, $180→$150, $160→$135, $100→$85, $50→$45) | Inspected deployed HTML output per division + fee combo | PASS | Verified via direct send_first_payment_email calls with mocked gmail client |
| 4 | Blast sends to all signed players only | Executed blast over 32 signed players from pod shell | PASS | 32 sent, 0 errors. Offered/none contracts correctly excluded (Jacelyn, Apaisa girls, etc.) |
| 5 | test_email param filters to one parent | Tests sent with test_email="draneylucas@gmail.com" | PASS | Single recipient when filter applied |
| 6 | CTA link uses base_url not frontend_url (initially bugged, fixed in hot-patch) | Inspect generated HTML | PASS | CTA → https://basketball-api.tail5b443a.ts.net/checkout/first-payment?token=... (basketball-api, not frontend app) |
| 7 | Blast delivery: 32 recipients, 0 failures | Production blast execution at 2026-04-11 18:41 UTC | PASS | All 32 messages accepted by Gmail API with unique IDs. Recipients: 19 Kings real + 8 Queens real (Jacelyn recovered separately) + 2 internal test records |
Verdict
PASS — production blast executed successfully. 32 emails delivered across the full signed-contract audience. Content iterated 3 times with Lucas + Marcus approval before send. Queens pink branding correctly applied to girls division players.
Discovered Issues
- Jacelyn Bronson incident: reverted her tier_change to Elite Queens after misreading the context (was actually Marcus-intended Local Queens move per batch #424). Recovered via silent team-move preserving her April 3 signature. See session notes.
- Email iteration: 3 content revisions needed post-initial-send (motivational → direct; "first practice" subject → neutral subject; base_url vs frontend_url bug). Suggests future SOP for email content approval.