Email blast — registration + coach invite emails (Phase 3b) #20

Closed
opened 2026-03-10 00:04:46 +00:00 by forgejo_admin · 0 comments

Lineage

plan-2026-03-08-tryout-prep → Phase 3b

Repo

forgejo_admin/basketball-api

User Story

As an admin,
I want to send personalized emails to 34 paid families and 4 coaches,
So that families can complete their child's tryout profile via a one-click link and coaches can begin onboarding immediately.

Context

The gmail-sdk integration already exists — services/email.py has get_gmail_client() and send_confirmation_email(). The GmailClient takes an account alias that maps to a token file: gmail-{account}.json in the secrets dir.

Available token: gmail-draneylucas.json at ~/secrets/google-oauth/. This is our POC sender. Later we switch to westsidebasketball when Marcus provides access.

Phase 3a (Issue #19) adds registration_token to the Parent model and POST /admin/generate-tokens. This issue builds on that — the email contains the personalized token link.

The existing email HTML in services/email.py uses blue/gold colors (#0a1628, #f5b731). Phase 3a will update the server-rendered pages to red/black. This issue must also use the Westside red/black brand in email templates.

config.py has a base_url setting (default http://localhost:8000) — emails need this to build full URLs like {base_url}/register?token=abc123. In production this must be https://basketball-api.tail5b443a.ts.net.

File Targets

Files to modify:

  • src/basketball_api/services/email.py — add send_registration_blast_email() and send_coach_invite_email() functions. Update existing send_confirmation_email() to use red/black brand. All email HTML must use Westside red/black brand tokens (--color-red: #d42026, --color-black: #0a0a0a, etc.)
  • src/basketball_api/routes/admin.py — add POST /admin/send-registration-emails and POST /admin/send-coach-invites endpoints (if file created by Phase 3a; otherwise create it)
  • src/basketball_api/main.py — include admin router if not already included

Files to read for context:

  • src/basketball_api/models.py — Parent.registration_token (added by Phase 3a), Coach.invite_token
  • src/basketball_api/config.py — base_url setting, gmail_secrets_dir
  • ~/west-side-basketball/css/style.css — design tokens for email HTML

Files NOT to touch:

  • src/basketball_api/routes/register.py — modified by Phase 3a
  • src/basketball_api/routes/webhooks.py — unchanged

Acceptance Criteria

  • send_registration_blast_email(tenant, parent, player) — sends branded HTML email with personalized token link {base_url}/register?token={parent.registration_token}. Subject: "Complete [Player Name]'s Westside Tryout Profile". Westside red/black branded.
  • send_coach_invite_email(tenant, coach) — sends branded HTML email with onboarding link {base_url}/coach/onboard?token={coach.invite_token}. Subject: "You're Invited to Coach — Westside Kings & Queens". Westside red/black branded.
  • Existing send_confirmation_email() updated to use red/black brand (replace blue/gold)
  • POST /admin/send-registration-emails — auth-protected (admin role). Generates tokens for any paid parent missing one (calls generate-tokens logic). Sends personalized email to every paid parent. Returns {"sent": N, "skipped": M, "errors": []}. Skips parents who already received an email (add registration_email_sent bool or check a flag).
  • POST /admin/send-coach-invites — auth-protected (admin role). Accepts JSON body with coach email list (or sends to all coaches in DB). Generates invite tokens if missing. Sends invite email to each coach. Returns {"sent": N, "errors": []}.
  • POC works with draneylucas as the tenant gmail_account — emails send from draneylucas@gmail.com
  • Email HTML renders correctly on mobile (Gmail app, iOS Mail)
  • Email contains: Westside logo/branding, player name, clear CTA button linking to token URL
  • Coach email contains: Westside branding, coach name, clear CTA button linking to onboarding URL
  • base_url config used for all links — no hardcoded URLs in email templates

Test Expectations

  • Unit test: send_registration_blast_email builds correct HTML with token link (mock GmailClient)
  • Unit test: send_coach_invite_email builds correct HTML with invite link (mock GmailClient)
  • Unit test: send-registration-emails endpoint sends to paid parents only, skips unpaid
  • Unit test: send-registration-emails is idempotent — re-running doesn't double-send
  • Unit test: send-coach-invites generates tokens for coaches without one
  • Unit test: admin endpoints require auth (return 401/403 without admin token)
  • Run command: pytest tests/ -v

Constraints

  • Use Westside red/black brand in all email HTML — same tokens as the server-rendered pages: #d42026 (red), #0a0a0a (black), #141414 (dark), #e2e8f0 (text)
  • Use config.settings.base_url for all links — never hardcode the URL
  • Gmail SDK pattern: GmailClient(account=tenant.gmail_account, secrets_dir=settings.gmail_secrets_dir)
  • POC sender: set Westside tenant gmail_account to "draneylucas" in tests/seeds. Production will be "westsidebasketball" later.
  • Idempotent sends: track which parents have been emailed. Don't re-send on second invocation.
  • Coach invite tokens have 7-day TTL (already in the model). Registration tokens do NOT expire.
  • Admin endpoints must be auth-protected via pal-e-auth (admin role)

Checklist

  • PR opened
  • Tests pass
  • No unrelated changes
  • Ruff format + lint clean
  • project-westside-basketball
  • plan-2026-03-08-tryout-prep → Phase 3b
  • Depends on: Issue #19 (Phase 3a — registration tokens + brand CSS)
  • Unblocks: sending emails to 34 families today
### Lineage `plan-2026-03-08-tryout-prep` → Phase 3b ### Repo `forgejo_admin/basketball-api` ### User Story As an admin, I want to send personalized emails to 34 paid families and 4 coaches, So that families can complete their child's tryout profile via a one-click link and coaches can begin onboarding immediately. ### Context The gmail-sdk integration already exists — `services/email.py` has `get_gmail_client()` and `send_confirmation_email()`. The GmailClient takes an `account` alias that maps to a token file: `gmail-{account}.json` in the secrets dir. Available token: `gmail-draneylucas.json` at `~/secrets/google-oauth/`. This is our POC sender. Later we switch to `westsidebasketball` when Marcus provides access. Phase 3a (Issue #19) adds `registration_token` to the Parent model and `POST /admin/generate-tokens`. This issue builds on that — the email contains the personalized token link. The existing email HTML in `services/email.py` uses blue/gold colors (`#0a1628`, `#f5b731`). Phase 3a will update the server-rendered pages to red/black. This issue must also use the Westside red/black brand in email templates. `config.py` has a `base_url` setting (default `http://localhost:8000`) — emails need this to build full URLs like `{base_url}/register?token=abc123`. In production this must be `https://basketball-api.tail5b443a.ts.net`. ### File Targets Files to modify: - `src/basketball_api/services/email.py` — add `send_registration_blast_email()` and `send_coach_invite_email()` functions. Update existing `send_confirmation_email()` to use red/black brand. All email HTML must use Westside red/black brand tokens (`--color-red: #d42026`, `--color-black: #0a0a0a`, etc.) - `src/basketball_api/routes/admin.py` — add `POST /admin/send-registration-emails` and `POST /admin/send-coach-invites` endpoints (if file created by Phase 3a; otherwise create it) - `src/basketball_api/main.py` — include admin router if not already included Files to read for context: - `src/basketball_api/models.py` — Parent.registration_token (added by Phase 3a), Coach.invite_token - `src/basketball_api/config.py` — base_url setting, gmail_secrets_dir - `~/west-side-basketball/css/style.css` — design tokens for email HTML Files NOT to touch: - `src/basketball_api/routes/register.py` — modified by Phase 3a - `src/basketball_api/routes/webhooks.py` — unchanged ### Acceptance Criteria - [ ] `send_registration_blast_email(tenant, parent, player)` — sends branded HTML email with personalized token link `{base_url}/register?token={parent.registration_token}`. Subject: "Complete [Player Name]'s Westside Tryout Profile". Westside red/black branded. - [ ] `send_coach_invite_email(tenant, coach)` — sends branded HTML email with onboarding link `{base_url}/coach/onboard?token={coach.invite_token}`. Subject: "You're Invited to Coach — Westside Kings & Queens". Westside red/black branded. - [ ] Existing `send_confirmation_email()` updated to use red/black brand (replace blue/gold) - [ ] `POST /admin/send-registration-emails` — auth-protected (admin role). Generates tokens for any paid parent missing one (calls generate-tokens logic). Sends personalized email to every paid parent. Returns `{"sent": N, "skipped": M, "errors": []}`. Skips parents who already received an email (add `registration_email_sent` bool or check a flag). - [ ] `POST /admin/send-coach-invites` — auth-protected (admin role). Accepts JSON body with coach email list (or sends to all coaches in DB). Generates invite tokens if missing. Sends invite email to each coach. Returns `{"sent": N, "errors": []}`. - [ ] POC works with `draneylucas` as the tenant gmail_account — emails send from draneylucas@gmail.com - [ ] Email HTML renders correctly on mobile (Gmail app, iOS Mail) - [ ] Email contains: Westside logo/branding, player name, clear CTA button linking to token URL - [ ] Coach email contains: Westside branding, coach name, clear CTA button linking to onboarding URL - [ ] `base_url` config used for all links — no hardcoded URLs in email templates ### Test Expectations - [ ] Unit test: `send_registration_blast_email` builds correct HTML with token link (mock GmailClient) - [ ] Unit test: `send_coach_invite_email` builds correct HTML with invite link (mock GmailClient) - [ ] Unit test: send-registration-emails endpoint sends to paid parents only, skips unpaid - [ ] Unit test: send-registration-emails is idempotent — re-running doesn't double-send - [ ] Unit test: send-coach-invites generates tokens for coaches without one - [ ] Unit test: admin endpoints require auth (return 401/403 without admin token) - Run command: `pytest tests/ -v` ### Constraints - Use Westside red/black brand in all email HTML — same tokens as the server-rendered pages: `#d42026` (red), `#0a0a0a` (black), `#141414` (dark), `#e2e8f0` (text) - Use `config.settings.base_url` for all links — never hardcode the URL - Gmail SDK pattern: `GmailClient(account=tenant.gmail_account, secrets_dir=settings.gmail_secrets_dir)` - POC sender: set Westside tenant `gmail_account` to `"draneylucas"` in tests/seeds. Production will be `"westsidebasketball"` later. - Idempotent sends: track which parents have been emailed. Don't re-send on second invocation. - Coach invite tokens have 7-day TTL (already in the model). Registration tokens do NOT expire. - Admin endpoints must be auth-protected via pal-e-auth (admin role) ### Checklist - [ ] PR opened - [ ] Tests pass - [ ] No unrelated changes - [ ] Ruff format + lint clean ### Related - `project-westside-basketball` - `plan-2026-03-08-tryout-prep` → Phase 3b - Depends on: Issue #19 (Phase 3a — registration tokens + brand CSS) - Unblocks: sending emails to 34 families today
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#20
No description provided.