131 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Notifications Templates
 | |
| 
 | |
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
 | |
| 
 | |
| Templates shape the payload rendered for each channel when a rule action fires. They are deterministic, locale-aware artefacts stored alongside rules so Notify.Worker replicas can render identical messages regardless of environment.
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 1. Template lifecycle
 | |
| 
 | |
| 1. **Authoring.** Operators create templates via the API (`POST /templates`) or UI. Each template binds to a channel type (`Slack`, `Teams`, `Email`, `Webhook`, `Custom`) and a locale.
 | |
| 2. **Reference.** Rule actions opt in by referencing the template key (`actions[].template`). Channel defaults apply when no template is specified.
 | |
| 3. **Rendering.** During delivery, the worker resolves the template (locale fallbacks included), executes it using the safe Handlebars-style engine, and passes the rendered payload plus metadata to the connector.
 | |
| 4. **Audit.** Rendered payloads stored in the delivery ledger include the `templateId` so operators can trace which text was used.
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 2. Template schema reference
 | |
| 
 | |
| | Field | Type | Notes |
 | |
| |-------|------|-------|
 | |
| | `templateId` | string | Stable identifier (UUID/slug). |
 | |
| | `tenantId` | string | Must match the tenant header in API calls. |
 | |
| | `channelType` | [`NotifyChannelType`](../ARCHITECTURE_NOTIFY.md#5-channels--connectors-plug-ins) | Determines connector payload envelope. |
 | |
| | `key` | string | Human-readable key referenced by rules (`tmpl-critical`). |
 | |
| | `locale` | string | BCP-47 tag, stored lower-case (`en-us`, `bg-bg`). |
 | |
| | `body` | string | Template body; rendered strictly without executing arbitrary code. |
 | |
| | `renderMode` | enum | `Markdown`, `Html`, `AdaptiveCard`, `PlainText`, or `Json`. Guides connector sanitisation. |
 | |
| | `format` | enum | `Slack`, `Teams`, `Email`, `Webhook`, or `Json`. Signals delivery payload structure. |
 | |
| | `description` | string? | Optional operator note. |
 | |
| | `metadata` | map<string,string> | Sorted map for automation (layout hints, fallback text). |
 | |
| | `createdBy`/`createdAt` | string?, instant | Auto-populated. |
 | |
| | `updatedBy`/`updatedAt` | string?, instant | Auto-populated. |
 | |
| | `schemaVersion` | string | Auto-upgraded on persistence. |
 | |
| 
 | |
| Templates are normalised: string fields trimmed, locale lower-cased, metadata sorted to preserve determinism.
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 3. Variables, helpers, and context
 | |
| 
 | |
| Templates receive a structured context derived from the Notify event, rule match, and rendering metadata.
 | |
| 
 | |
| | Path | Description |
 | |
| |------|-------------|
 | |
| | `event.*` | Canonical event envelope (`kind`, `tenant`, `ts`, `actor`). |
 | |
| | `event.scope.*` | Namespace, repository, digest, image, component identifiers, labels, attributes. |
 | |
| | `payload.*` | Raw event payload (e.g., `payload.verdict`, `payload.delta.*`, `payload.links.*`). |
 | |
| | `rule.*` | Rule descriptor (`ruleId`, `name`, `labels`, `metadata`). |
 | |
| | `action.*` | Action descriptor (`actionId`, `channel`, `digest`, `throttle`, `metadata`). |
 | |
| | `policy.*` | Policy metadata when supplied (`revisionId`, `name`). |
 | |
| | `topFindings[]` | Top-N findings summarised for convenience (vulnerability ID, severity, reachability). |
 | |
| | `digest.*` | When rendering digest flushes: `window`, `openedAt`, `itemCount`. |
 | |
| 
 | |
| Built-in helpers mirror the architecture dossier:
 | |
| 
 | |
| | Helper | Usage |
 | |
| |--------|-------|
 | |
| | `severity_icon severity` | Returns emoji/text badge representing severity. |
 | |
| | `link text url` | Produces channel-safe hyperlink. |
 | |
| | `pluralize count "finding"` | Adds plural suffix when `count != 1`. |
 | |
| | `truncate text maxLength` | Cuts strings while preserving determinism. |
 | |
| | `code text` | Formats inline code (Markdown/HTML aware). |
 | |
| 
 | |
| Connectors may expose additional helpers via partials, but must remain deterministic and side-effect free.
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 4. Sample templates
 | |
| 
 | |
| ### 4.1 Slack (Markdown + block kit)
 | |
| 
 | |
| ```hbs
 | |
| {{#*inline "findingLine"}}
 | |
| - {{severity_icon severity}} {{vulnId}} ({{severity}}) in `{{component}}`
 | |
| {{/inline}}
 | |
| 
 | |
| *:rotating_light: {{payload.summary.total}} findings {{#if payload.delta.newCritical}}(new critical: {{payload.delta.newCritical}}){{/if}}*
 | |
| 
 | |
| {{#if topFindings.length}}
 | |
| Top findings:
 | |
| {{#each topFindings}}{{> findingLine}}{{/each}}
 | |
| {{/if}}
 | |
| 
 | |
| {{link "Open report in Console" payload.links.ui}}
 | |
| ```
 | |
| 
 | |
| ### 4.2 Email (HTML + text alternative)
 | |
| 
 | |
| ```hbs
 | |
| <h2>{{payload.verdict}} for {{event.scope.repo}}</h2>
 | |
| <p>{{payload.summary.total}} findings ({{payload.summary.blocked}} blocked, {{payload.summary.warned}} warned)</p>
 | |
| <table>
 | |
|   <thead><tr><th>Finding</th><th>Severity</th><th>Package</th></tr></thead>
 | |
|   <tbody>
 | |
|     {{#each topFindings}}
 | |
|     <tr>
 | |
|       <td>{{this.vulnId}}</td>
 | |
|       <td>{{this.severity}}</td>
 | |
|       <td>{{this.component}}</td>
 | |
|     </tr>
 | |
|     {{/each}}
 | |
|   </tbody>
 | |
| </table>
 | |
| <p>{{link "View full analysis" payload.links.ui}}</p>
 | |
| ```
 | |
| 
 | |
| When delivering via email, connectors automatically attach a plain-text alternative derived from the rendered content to preserve accessibility.
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 5. Preview and validation
 | |
| 
 | |
| - `POST /channels/{id}/test` accepts an optional `templateId` and sample payload to produce a rendered preview without dispatching the event. Results include channel type, target, title/summary, locale, body hash, and connector metadata.
 | |
| - UI previews rely on the same API and highlight connector fallbacks (e.g., Teams adaptive card vs. text fallback).
 | |
| - Offline Kit scenarios can call `/internal/notify/templates/normalize` to ensure bundled templates match the canonical schema before packaging.
 | |
| 
 | |
| ---
 | |
| 
 | |
| ## 6. Best practices
 | |
| 
 | |
| - Keep channel-specific limits in mind (Slack block/character quotas, Teams adaptive card size, email line length). Lean on digests to summarise long lists.
 | |
| - Provide locale-specific versions for high-volume tenants; Notify selects the closest locale, falling back to `en-us`.
 | |
| - Store connector-specific hints (`metadata.layout`, `metadata.emoji`) in template metadata rather than rules when they affect rendering.
 | |
| - Version template bodies through metadata (e.g., `metadata.revision: "2025-10-28"`) so tenants can track changes over time.
 | |
| - Run test previews whenever introducing new helpers to confirm body hashes remain stable across environments.
 | |
| 
 | |
| ---
 | |
| 
 | |
| > **Imposed rule reminder:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
 |