Architecture: Email System

arch-email Doc

architecture active

Email System Architecture

Overview

Email is a per-project concern, not platform infrastructure. basketball-api owns all Westside email via gmail-sdk (direct import, no microservice indirection). pal-e-mail is archived — it was never consumed.

Components

  • gmail-sdk — shared Python library. OAuth token management, send, refresh.
  • services/email.py — send functions, template loading, EmailLog persistence
  • services/email_queries.py — audience query registry (unsigned_contracts, etc.)
  • services/outbox.py — async event-driven sends (contract_signed)
  • brand.py — design tokens (colors, fonts, CSS)
  • templates/email/*.mjml — MJML source files, compiled during docker build
  • load_email_template() — reads compiled HTML, replaces {{key}} placeholders
  • EmailLog — audit trail: tenant, parent, player, type, gmail_message_id

Layouts

  • notification — "X happened" (headline + body + footer)
  • action — "Do this thing" (headline + body + CTA button + footer)
  • announcement — rich content (headline + sections + optional CTA + footer)

Email Flow

MJML source → compile during docker build → baked into image → load_email_template() at runtime → gmail-sdk send → EmailLog

Preview & Approval Workflow

Development (fast iteration)

  • Run mjml --watch templates/email/*.mjml -o templates/email/compiled/ — recompiles on save
  • Open compiled HTML in Chrome DevTools at 390px width (mobile viewport)
  • Edit MJML → save → refresh browser → see changes instantly
  • This gets layout and branding 90% right. Not pixel-perfect for Gmail but close enough for structural work.

Approval (the real gate)

  • Deploy image with compiled templates (or use test endpoint on dev)
  • Hit /email/blast with test_email=lucas@... — sends one real email
  • Check on phone (Gmail app). This is the truth — real renderer, real viewport.
  • Forward to Marcus if he needs to approve content
  • Lucas approves → blast to full audience

Why not a live preview server?

Gmail's HTML renderer is proprietary. No local tool can perfectly simulate it. Browsers support CSS that Gmail strips (Grid, Flexbox, custom properties, <style> blocks). MJML handles the hard part by compiling to table-based inline styles, but the only true test is sending a real email. Paid services (Litmus, Email on Acid) render across 90+ clients — overkill for 3 layouts targeting mostly Gmail mobile.

Decisions

  • No CSS Grid/Flexbox/custom properties in email (Gmail strips them). MJML compiles to table-based inline styles.
  • No Jinja2 — simple {{key}} string replacement via load_email_template()
  • Mobile-first via MJML default column stacking
  • test_email query param on all blast endpoints for safety
  • Browser preview for dev speed, real Gmail send for approval gate