# NotificationTemplate

## Description

NotificationTemplate stores the channel-specific message content used by the dispatcher to materialize each delivery. Each template is uniquely keyed by the triple `(eventType, channelId, locale)` and carries a `subject`, a plain-text `body`, an optional `htmlBody` (used by EMAIL and any other rich channels), and a declared `variableSchema` describing the variables the template expects at render time. The notification module is portal-agnostic: deep links and any other surface-specific URLs are resolved by the application layer and passed in as variables — templates never construct portal URLs.

Templates are rendered at dispatch time. The dispatcher invokes the renderer with `(eventType, channelId, locale, payloadVars)`, the template feature looks up a row by the exact triple, and on miss falls back to `(eventType, channelId, defaultLocale)` before failing with `TEMPLATE_NOT_FOUND`. Once a template is found, `payloadVars` is validated against `variableSchema` (missing required vars → `TEMPLATE_VAR_VALIDATION_FAILED`) and the renderer performs simple `{{variable}}` substitution into `subject`, `body`, and `htmlBody`. The templating language is intentionally minimal — substitution only, no conditional logic, no loops, no filters — so the variable contract, validation, and channel-specific escaping rules stay trivially auditable. PII is handled by holding only the structural template in the database; sensitive variable values are resolved per dispatch, and the rendered output is persisted on the per-recipient Notification row (`subject` / `body` / `htmlBody`, alongside the input `payloadVars`) — never on the template row — where it is immutable except for GDPR anonymization, which scrubs the rendered fields together with `payloadVars`.

## Domain Model Definitions

### Model type

Standard

### Command Definitions

- [createNotificationTemplate](../command/CreateNotificationTemplate.md) - Create a new template for a `(eventType, channelId, locale)` triple with subject, body, optional htmlBody, and variable schema
- [updateNotificationTemplate](../command/UpdateNotificationTemplate.md) - Update an existing template's content or variable schema; changes are picked up by subsequent renders
- [deleteNotificationTemplate](../command/DeleteNotificationTemplate.md) - Remove a template row so subsequent renders for that triple fall back to the default-locale row or fail

### Query Definitions

- [getNotificationTemplate](../query/GetNotificationTemplate.md) - Retrieve a single template row by its `(eventType, channelId, locale)` triple
- [listNotificationTemplates](../query/ListNotificationTemplates.md) - List templates with optional filtering by eventType, channelId, or locale
- [renderNotificationTemplate](../query/RenderNotificationTemplate.md) - Look up and render a template against `payloadVars`, applying locale fallback and variable validation

### Models

- NotificationTemplate

### Invariants

- The triple `(eventType, channelId, locale)` is unique across the catalog; a second row with the same triple is rejected
- `subject` and `body` must be non-blank
- `htmlBody` is optional and is only meaningful for channels whose `capabilities.supportsHtmlBody = true`
- `variableSchema` declares every variable referenced from `subject`, `body`, and `htmlBody`; a referenced variable not declared in the schema is a template-authoring error caught at template save time
- Updating a template does not mutate the rendered content of Notification rows already persisted before the update — each Notification row immutably carries the `subject` / `body` / `htmlBody` rendered at its dispatch time alongside `payloadVars`, and is never retroactively re-rendered
- Required variables declared in `variableSchema` must be supplied by the emitter; missing required vars cause the render to fail with `TEMPLATE_VAR_VALIDATION_FAILED` before any interpolation runs
- Optional variables declared in `variableSchema` render as the empty string when not supplied, never as an error
- Variables supplied in `payloadVars` but not declared in `variableSchema` are silently ignored
- `{{variable}}` substitution is the only supported templating construct; tokens that look like loops or conditionals are treated as literal text
- Plain-text `body` interpolation inserts variable values verbatim; `htmlBody` interpolation HTML-escapes user-supplied variable values so embedded markup cannot break the document or inject script tags

### Relationships

- **References NotificationChannel as channelId**: the template's channel is resolved against the channel registry, including its capabilities for capability-based branching
- **References EventCategoryBinding as eventType**: the eventType key must be present in the seeded event catalog for the template to be reachable at dispatch time
- **Read by Notification at dispatch time**: the dispatcher renders this template against `payloadVars` and persists the resulting subject, body, and htmlBody onto the produced Notification row
