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

@@ -19,8 +19,8 @@ SMS flow: On-prem TapTrack polls Hub every 30s → Hub queues SMS jobs → Hub s
## Current Milestone
**v1.0 — Foundation & Core Services**
Status: Phase 8 complete — Phase 9 next
Phases: 8 of 15 complete
Status: Phase 9 complete — Phase 10 next
Phases: 9 of 15 complete
---
@@ -36,7 +36,7 @@ Phases: 8 of 15 complete
| 6 | Super Admin Dashboard UI | 1 | ✅ Complete | 2026-03-16 |
| 7 | School Admin Portal UI | 1 | ✅ Complete | 2026-03-16 |
| 8 | Billing Engine + Invoice PDF | 1 | ✅ Complete | 2026-03-16 |
| 9 | Email Dispatcher | TBD | Not started | |
| 9 | Email Dispatcher | 1 | ✅ Complete | 2026-03-16 |
| 10 | Support Ticket System | TBD | Not started | — |
| 11 | Monthly Report Generation | TBD | Not started | — |
| 12 | On-Prem Monthly Report Pull | TBD | Not started | — |

View File

@@ -3,16 +3,16 @@
## Current Position
Milestone: v1.0 — Foundation & Core Services
Phase: 8 of 15 (Billing Engine + Invoice PDF — complete)
Plan: Phase 8 complete — Phase 9 next
Status: **Phase 8 applied — ready to begin Phase 9**
Last activity: 2026-03-16 — Phase 8 complete (invoice PDF via Jinja2+WeasyPrint, mark-paid modal, overdue escalation with 7d warning + 30d suspension, bug fix in billing task)
Phase: 9 of 15 (Email Dispatcher — complete)
Plan: Phase 9 complete — Phase 10 next
Status: **Phase 9 applied — ready to begin Phase 10**
Last activity: 2026-03-16 — Phase 9 complete (HTML email templates, email_logs table, enhanced send_email with template rendering + delivery logging + retries, email logs UI + test-email)
## Loop Position
```
PLAN ──▶ APPLY ──▶ UNIFY
· · · [No active plan — Phase 9 planning next]
· · · [No active plan — Phase 10 planning next]
```
## Progress
@@ -27,7 +27,7 @@ PLAN ──▶ APPLY ──▶ UNIFY
- Phase 6 (Super Admin Dashboard UI): [██████████] 100% ✓
- Phase 7 (School Admin Portal UI): [██████████] 100% ✓
- Phase 8 (Billing Engine + Invoice PDF): [██████████] 100% ✓
- Phase 9 (Email Dispatcher): [░░░░░░░░░░] 0%
- Phase 9 (Email Dispatcher): [██████████] 100% ✓
- Phase 10 (Support Ticket System): [░░░░░░░░░░] 0%
- Phase 11 (Monthly Report Generation): [░░░░░░░░░░] 0%
- Phase 12 (On-Prem Monthly Report Pull): [░░░░░░░░░░] 0%
@@ -37,14 +37,14 @@ PLAN ──▶ APPLY ──▶ UNIFY
## Next Action
Run: `/paul:plan` for Phase 9Email Dispatcher
Resume file: .paul/ROADMAP.md → Phase 9
Run: `/paul:plan` for Phase 10Support Ticket System
Resume file: .paul/ROADMAP.md → Phase 10
## Repo
Remote: TBD (new Gitea repo)
Branch: master
Last commit: feat(phase-8): billing engine + invoice PDF + mark-paid + overdue escalation
Last commit: feat(phase-9): email dispatcher — HTML templates, delivery log, test endpoint
## Tech Stack

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)`