131 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			131 lines
		
	
	
		
			6.4 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`](../modules/notify/architecture.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.
 |