Validation: Issue #369 — send_first_payment_email + POST /admin/email/first-payment blast

validation-369-2026-04-11 Doc

validation pass

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.