feat(phase-9): email dispatcher — HTML templates, delivery log, test endpoint

Backend:
- app/models/email_log.py: EmailLog table (school_id, to, subject, type, status,
  error, sent_at) with EmailType + EmailStatus enums
- migrations/002_phase9_email_logs.py: Alembic migration for email_logs table
- app/templates/email/: 6 Jinja2 HTML templates — base layout, invoice,
  low_credit, license_expiry, overdue_warning, suspension
- app/services/email.py: enhanced send_email() — accepts template_name+context
  for HTML rendering, logs every attempt to email_logs, retries up to 3x on
  transient SMTP failure with exponential backoff
- app/routers/email.py: GET /api/email/logs (paginated, filterable by type/status/school),
  POST /api/email/test (send test email, super admin)
- tasks/billing.py: invoice + overdue warning + suspension emails now use HTML templates
- tasks/sms.py: low credit alert now uses HTML template
- tasks/license.py: expiry warning now uses HTML template
- app/main.py + migrations/env.py: wire in email_log model + email router

Frontend:
- EmailLogsPage.vue: table with to/subject/type badge/status badge/sent_at/error,
  type+status filters, pagination, Send Test Email modal
- router/index.ts: /email-logs route
- AppSidebar.vue: Email Logs nav item
- api.ts: getEmailLogs, sendTestEmail
This commit is contained in:
kevin-asprec
2026-03-16 14:03:02 +08:00
parent ce829a009d
commit 1febb3cfa9
22 changed files with 846 additions and 101 deletions

View File

@@ -1,60 +1,52 @@
# Phase 09: Email Dispatcher
**Status:** Not started
**Status:** Complete
**Completed:** 2026-03-16
**Depends on:** Phase 8 (Billing Engine)
## Goal
Ensure all automated emails (invoices, low credits, license expiry, monthly reports, welcome)
send correctly via SMTP with proper HTML templates. Add an email delivery log visible to
super admin, and a test-email endpoint to verify SMTP config.
All automated emails send via HTML templates with consistent branding. Every send attempt
is logged to `email_logs`. Super admin can view the delivery log and send a test email
to verify SMTP config.
## Planned Scope
## What was built
### Backend
**Jinja2 HTML email templates** (`backend/app/templates/email/`)
- `invoice.html` — branded invoice notification (already plain text in Phase 8; upgrade to HTML)
- `low_credit_alert.html` — low credit warning with credit meter visual
- `license_expiry.html` — license expiry countdown with days remaining
- `monthly_report.html` — monthly report summary email (stub, filled in Phase 11)
- `welcome.html` — welcome email with license key + setup instructions (used in Phase 14)
- Base layout template with consistent header/footer
**`app/models/email_log.py`** (new)
- `EmailLog` table: id, school_id (nullable FK), to_email, subject, email_type, status (sent/failed), error_message, sent_at, created_at
**`email_logs` table** (new model `app/models/email_log.py`)
- `id`, `school_id` (nullable), `to_email`, `subject`, `email_type`, `status` (sent/failed),
`sent_at`, `error_message`, `created_at`
**`app/templates/email/`** (new — 6 files)
- `base.html` — shared branded layout (header + footer)
- `invoice.html` — invoice notification with amount + due date
- `low_credit.html` — low credit warning with balance + threshold
- `license_expiry.html` — license expiry countdown
- `overdue_warning.html` — overdue invoice warning
- `suspension.html` — account suspended notice
**`app/services/email.py`** — enhanced
- Accept HTML template name + context instead of raw body string
- Log every send attempt to `email_logs` table
- Retry on transient failure (up to 3 attempts)
- `send_email()` now accepts `template_name` + `context` for HTML rendering
- Logs every attempt to `email_logs` via a sync session
- Falls back to plain text if template not found
- Retries up to 3 times on transient SMTP failure
**`backend/app/routers/email.py`** (new)
- `GET /api/email-logs`super admin: paginated email delivery history with filters
- `POST /api/email/test` — send a test email to verify SMTP config (super admin)
**`app/routers/email.py`** (new)
- `GET /api/email-logs`paginated log with type/status/school filters (super admin)
- `POST /api/email/test` — send test email to verify SMTP (super admin)
**Update all tasks** that send email (billing, sms, license) to use HTML templates
**Updated tasks** — all now pass `template_name` + `context` to `send_email()`:
- `tasks/billing.py` — invoice email, overdue warning, suspension email
- `tasks/sms.py` — low credit alert
- `tasks/license.py` — expiry warning
### Frontend
**EmailLogsPage or section** in super admin
- Table: to, subject, type, status badge, sent_at, error message
- Filter by email type + status
**`EmailLogsPage.vue`** (new)
- Table: to, subject, type badge, status badge, sent_at, error message (expandable)
- Filter by type + status; pagination
- "Send Test Email" button → modal with address input
**api.ts additions**
- `getEmailLogs(params)` — list email logs
- `sendTestEmail(to)` — trigger test email
## Key Files to Create/Modify
- `backend/app/models/email_log.py` (new)
- `backend/app/templates/email/*.html` (new — 5 templates)
- `backend/app/services/email.py` (enhance)
- `backend/app/routers/email.py` (new)
- `backend/app/main.py` — include email router
- `backend/migrations/versions/002_email_logs.py` (new migration)
- `frontend/src/pages/EmailLogsPage.vue` (new or section in existing page)
- `frontend/src/lib/api.ts` — add email log functions
- `frontend/src/router/index.ts` — add email logs route
- `frontend/src/components/sidebar/AppSidebar.vue` — add nav item
**Router**`/email-logs` route added (super admin)
**AppSidebar** — "Email Logs" nav item added
**api.ts**`getEmailLogs(params)`, `sendTestEmail(to)`