Overview
Platform admins and tenants communicate with their users via automated emails (milestone approved, dispute filed, escrow funded). Today these templates are hardcoded strings. A template editor gives non-engineers control over wording, branding, and personalisation without a deployment.
What to build
notification_templates table: { id, tenant_id (nullable for global), event_type: string, subject: string, body_html: string, body_text: string, variables: JSONB, updated_at, updated_by }
- Template engine: use Handlebars for variable interpolation (
{{escrow.amount}}, {{user.name}}); reject templates containing {{{{raw}}}} blocks (security: no raw HTML injection)
GET /api/v1/admin/notification-templates — list all templates; optionally filtered by event_type
PUT /api/v1/admin/notification-templates/:event_type — upsert a template for a given event; validate Handlebars syntax before saving
POST /api/v1/admin/notification-templates/:event_type/preview — body: { variables: {} }; renders the template with provided variables and returns { subject, body_html, body_text }; never sends an email
- Frontend
NotificationTemplateEditor component:
- Left panel: CodeMirror editor for HTML body with syntax highlighting and variable autocomplete from the schema
- Right panel: live preview rendered in a sandboxed iframe (CSP:
sandbox="allow-same-origin")
- Variable palette: all available variables for the selected event type displayed as clickable chips that insert
{{variable}} at cursor
- Undo/redo for editor history
Save and Reset to default buttons; reset confirmation modal
- Variable schema: stored in
notification_templates.variables JSONB; defines name, type, example value for each variable — used for autocomplete and preview
Acceptance Criteria
Technical notes
- CodeMirror 6 with
@codemirror/lang-html and a custom completion source for {{ variables
- System defaults live in
src/notifications/templates/*.hbs files; loaded at startup and registered as immutable defaults
Overview
Platform admins and tenants communicate with their users via automated emails (milestone approved, dispute filed, escrow funded). Today these templates are hardcoded strings. A template editor gives non-engineers control over wording, branding, and personalisation without a deployment.
What to build
notification_templatestable:{ id, tenant_id (nullable for global), event_type: string, subject: string, body_html: string, body_text: string, variables: JSONB, updated_at, updated_by }{{escrow.amount}},{{user.name}}); reject templates containing{{{{raw}}}}blocks (security: no raw HTML injection)GET /api/v1/admin/notification-templates— list all templates; optionally filtered byevent_typePUT /api/v1/admin/notification-templates/:event_type— upsert a template for a given event; validate Handlebars syntax before savingPOST /api/v1/admin/notification-templates/:event_type/preview— body:{ variables: {} }; renders the template with provided variables and returns{ subject, body_html, body_text }; never sends an emailNotificationTemplateEditorcomponent:sandbox="allow-same-origin"){{variable}}at cursorSaveandReset to defaultbuttons; reset confirmation modalnotification_templates.variablesJSONB; defines name, type, example value for each variable — used for autocomplete and previewAcceptance Criteria
{{triple_stache}}}(raw HTML) is blocked at save time with a clear error{{Reset to defaultrestores the system default template (not an empty string); system defaults are code-definedTechnical notes
@codemirror/lang-htmland a custom completion source for{{variablessrc/notifications/templates/*.hbsfiles; loaded at startup and registered as immutable defaults