project-pal-e-mail updated 2026-03-22Vision
Centralized, multi-tenant email service for the Pal-E platform. Any project sends branded emails via one API call. Email content lives here — not in app repos. Sending infrastructure (Gmail OAuth, token refresh, delivery logging) is platform-level, not duplicated per project. Templates compile from MJML repos and deploy to MinIO CDN. Custom composed emails wrap in project-specific brand layouts. Self-hosted, zero SaaS dependencies.
User Stories
| Role | Key | Story | Success Metric |
|---|---|---|---|
| Platform Admin (Lucas) | trigger-send | Trigger bulk emails from admin panels (jersey reminders, tryout announcements, roster exports) without managing Gmail OAuth in each app repo | One POST /send call per recipient. Zero email code in app repos. |
| Platform Admin (Lucas) | delivery-log | See who got what email, when, and whether it failed — across all projects from one log | GET /log with project, recipient, template, timestamp, status filters |
| Platform Admin (Lucas) | schedule-send | Schedule an email for future delivery so I can prepare now and send later | POST /send with send_at queues for delivery. GET /scheduled to view/cancel pending. |
| Platform Admin (Lucas) | sender-management | See which Gmail senders are healthy vs expired and re-authorize from a browser — no SSH or kubectl | GET /senders shows status. Browser-based re-auth flow when token expires. |
| Platform Admin (Lucas) | template-update | Update email templates without redeploying app code | CI compiles MJML → uploads to MinIO CDN → pal-e-mail fetches at send time |
| App Service (automated) | api-send | Send registration confirmations, password resets, and notifications via centralized service | App calls POST /send. No Gmail logic, no OAuth, no HTML rendering in app repo. |
| End User (parents) | branded-email | Receive professional, mobile-responsive emails that render in Gmail/Outlook/Apple Mail | MJML-compiled HTML with brand wrapper renders correctly across all major clients |
Consumer Projects
Projects that send email through pal-e-mail:
| Project | Sender | Email Types | Status |
|---|---|---|---|
| Westside Basketball | westsidebasketball@gmail.com | Jersey reminders, tryout announcements, roster exports, profile reminders, registration confirmations, password resets | Phase 4 migration (currently in basketball-api) |
| mcd-tracker | (future) | Receipt confirmations, weekly summaries | Backlog |
| pal-e-docs | (future) | System notifications, share invitations | Backlog |
Plan
Active: plan-pal-e-mail (to be created)
Board
board-pal-e-mail (to be created)
Status
Phase 2 complete. Core Send API merged (PR #4). POST /send supports template mode (CDN fetch + {{var}} replacement), custom HTML mode (brand wrapper), and plain text. GET /log provides cross-project queryable email history with filtering and pagination. 28 tests, Woodpecker CI green. Phase 1 deployed: pod healthy, ArgoCD synced, Postgres with email_log table, Gmail OAuth PVC. MinIO network policy applied (pal-e-platform#144 — merged). Template CDN fetching unblocked.
Milestones
None yet.
Architecture
System Overview
Any App API pal-e-mail service
POST /send ──────────────────────► FastAPI
{sender, to, template, data} │
├─► Fetch template from MinIO CDN (public URL)
│ https://minio-api.ts.net/assets/email-templates/{project}/
│
├─► String replace {{variables}}
│
├─► Load Gmail OAuth creds for sender
│ /secrets/gmail/{sender}.json
│
├─► Gmail API send
│
└─► Log to email_log table (project, to, template, status)
Key Architectural Decisions
- Templates on MinIO CDN, not ConfigMaps — MJML CI compiles and uploads to
assets/email-templates/{project}/. Public-read. Service fetches via HTTP at send time. No k8s volume mounts, no pod restarts for template changes. - minio-api for uploads, CDN for reads — CI uploads templates through authenticated minio-api. Service reads via public CDN URL. Clean separation.
- Multi-tenant Gmail OAuth — one token file per sender identity. Service loads the right credentials based on
senderfield. Tokens on writable volume for refresh. - Template OR custom HTML — POST /send accepts either
template(fetched from CDN) orhtml(raw HTML, wrapped in brand layout). Supports both templated and ad-hoc emails. - No Jinja2, no template engine — simple
{{variable}}string replacement. MJML handles the rendering. Python handles the data injection. - Westside-emails migrates in — existing
westside-emailsrepo continues to own MJML source. CI uploads compiled HTML to MinIO. pal-e-mail fetches from there.
Repos
| Repo | Platform | Role | Status |
|---|---|---|---|
pal-e-mail |
Forgejo | FastAPI email sending service — multi-tenant, template or custom, Gmail OAuth | planned |
westside-emails |
Forgejo | MJML templates for Westside Basketball (compiles to HTML, uploads to MinIO) | active (migrates to CDN delivery) |
Inbox
Query: list_board_items(board_slug="board-pal-e-mail", column="backlog")