> ## Documentation Index
> Fetch the complete documentation index at: https://docs.svantic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Notification Channels & Templates

# Notification Channels & Templates

Svantic delivers notifications through configurable channels — email, Slack, webhook, SMS, and phone. Each channel has its own message format, delivery mechanism, and capabilities. Templates control how messages are rendered for each channel and event type.

***

## Channel Types

### Email

Delivers formatted HTML emails via the platform's SMTP service.

**Best for**: Approval requests, daily digests, operation summaries, compliance audit trails.

**Capabilities**: Rich HTML formatting, deep links to the dashboard, supports attachments for reports.

**Configuration**:

```json theme={null}
{
  "type": "email",
  "name": "Ops team",
  "config": {
    "recipients": ["ops@company.com", "oncall@company.com"]
  }
}
```

### Slack

Delivers formatted messages to a Slack channel using the Slack API. Supports interactive buttons for approvals directly within Slack.

**Best for**: Real-time alerts, team-visible approval requests, operational notifications.

**Capabilities**: Block Kit formatting, interactive Approve/Deny buttons, threaded replies, severity-coded emoji indicators.

**Configuration**:

```json theme={null}
{
  "type": "slack",
  "name": "Engineering alerts",
  "config": {
    "bot_token": "xoxb-...",
    "channel_id": "C0123456789"
  }
}
```

For step-by-step instructions on creating a Slack App and obtaining the bot token and channel ID, see the [Slack Integration guide](./slack-integration).

**Interactive resolution**: When an approval notification includes simple options (Approve/Deny), Slack renders inline buttons. Clicking a button resolves the pending request immediately via the Slack interactive webhook. For complex forms or sensitive fields, a "Complete in Dashboard" button links to the approval page.

### Webhook

Delivers a signed JSON payload to any HTTP endpoint. Designed for integration with external systems — PagerDuty, Opsgenie, custom automation, ticketing systems.

**Best for**: System-to-system integration, triggering external workflows, audit logging to external SIEM.

**Capabilities**: JSON payload with full event data, HMAC-SHA256 signature for verification, callback\_token for resolution, retry with exponential backoff (3 attempts: 1s, 5s, 30s).

**Configuration**:

```json theme={null}
{
  "type": "webhook",
  "name": "PagerDuty",
  "config": {
    "url": "https://events.pagerduty.com/integration/...",
    "secret": "optional-hmac-secret"
  }
}
```

**Outbound payload structure**:

```json theme={null}
{
  "type": "approval_required | alert | operation_complete | informational",
  "message_id": "msg-abc123",
  "session_id": "session-456",
  "tenant_id": "acme",
  "callback_token": "base64url-token",
  "resolve_url": "https://gateway/messages/msg-abc123/resolve",
  "severity": "critical | warning | info",
  "timestamp": "2026-04-17T10:30:00Z",
  "event": { ... },
  "rule_name": "Agent unhealthy"
}
```

The `callback_token` allows the receiving system to resolve pending requests without Svantic credentials. POST the token back to the `resolve_url` with a `resolution` object.

### SMS

Delivers short text messages via Twilio SMS.

**Best for**: Urgent approval requests when approvers are away from their desk, critical alerts that need immediate attention.

**Capabilities**: Short-form messages (160 chars for single segment), deep link to dashboard for resolution.

**Configuration**:

```json theme={null}
{
  "type": "sms",
  "name": "On-call approver",
  "config": {
    "phone_numbers": ["+14155551234"],
    "twilio_account_sid": "AC...",
    "twilio_auth_token": "...",
    "twilio_from_number": "+14155550000"
  }
}
```

### Phone

Delivers voice notifications via Twilio outbound calls. Uses text-to-speech (TTS) for the message and DTMF (keypad) input for resolution.

**Best for**: Critical escalations when other channels haven't been resolved, high-stakes approvals requiring verbal confirmation.

**Capabilities**: TTS message delivery, DTMF input collection (press 1 to approve, 2 to deny), retry on no-answer, voicemail detection.

**Configuration**:

```json theme={null}
{
  "type": "phone",
  "name": "VP Engineering escalation",
  "config": {
    "phone_numbers": ["+14155551234", "+14155555678"],
    "twilio_account_sid": "AC...",
    "twilio_auth_token": "...",
    "twilio_from_number": "+14155550000",
    "retry_on_no_answer": true,
    "max_retries": 2
  }
}
```

**Resolution via DTMF**: The TTS script reads the approval context and offers keypad options. Pressing a key immediately resolves the pending request via the Twilio status callback.

**Escalation**: If the first phone number doesn't answer, the system tries the next number in the list. If all numbers fail, the request remains pending (and may expire per the policy's expiration setting).

### In-App

Always enabled, not configurable. Every notification appears in the dashboard's notification bell. Approval requests appear in the Approvals page.

***

## Template System

Templates control how each notification is rendered for each channel. The system uses a **master + tenant** architecture.

### Architecture

**Template Master** (built-in defaults) → seeded on first boot → **Tenant Templates** (per-tenant, in database) → runtime lookup → **Template Registry** → fallback chain → **Rendered Notification**

### Template Master

The template master contains the default templates for every supported combination of event type and channel type. These are shipped with the platform and represent the recommended notification format.

**On first boot** (database seed): The master templates are written to the root tenant's `notification_templates` table. These serve as the source of truth for all future tenant creation.

**On tenant creation**: The root tenant's templates are copied to the new tenant. The new tenant immediately has a full set of working templates that can be customized independently.

### Template Structure

Each template is identified by three keys:

| Field          | Description                                                                      |
| -------------- | -------------------------------------------------------------------------------- |
| `tenant_id`    | Which tenant this template belongs to                                            |
| `event_type`   | Which event triggers this template (e.g., `guard.approval_required`)             |
| `channel_type` | Which channel this template is for (`email`, `slack`, `webhook`, `sms`, `phone`) |

Template content varies by channel:

| Channel     | Template fields                                                                      |
| ----------- | ------------------------------------------------------------------------------------ |
| **Email**   | `subject` (text with variables), `html_body` (HTML with variables)                   |
| **Slack**   | `blocks` (Block Kit JSON with variables), `fallback_text`                            |
| **Webhook** | `payload_schema` (JSON structure with variables)                                     |
| **SMS**     | `message` (plain text with variables, max \~300 chars)                               |
| **Phone**   | `tts_script` (text-to-speech script with variables), `dtmf_options` (keypad mapping) |

### Template Variables

Templates can reference variables from the notification context:

| Variable                                 | Description                                               |
| ---------------------------------------- | --------------------------------------------------------- |
| `&#123;&#123;event_type&#125;&#125;`     | The event type (e.g., `guard.approval_required`)          |
| `&#123;&#123;severity&#125;&#125;`       | Severity level: `info`, `warning`, `critical`             |
| `&#123;&#123;agent_type&#125;&#125;`     | The agent involved                                        |
| `&#123;&#123;session_id&#125;&#125;`     | Session identifier                                        |
| `&#123;&#123;message_id&#125;&#125;`     | Message/task identifier                                   |
| `&#123;&#123;tenant_id&#125;&#125;`      | Tenant identifier                                         |
| `&#123;&#123;rule_name&#125;&#125;`      | Policy or alert rule name that triggered the notification |
| `&#123;&#123;title&#125;&#125;`          | Human-readable notification title                         |
| `&#123;&#123;description&#125;&#125;`    | Contextual description                                    |
| `&#123;&#123;dashboard_url&#125;&#125;`  | Link to the relevant dashboard page                       |
| `&#123;&#123;resolve_url&#125;&#125;`    | URL for resolving pending requests                        |
| `&#123;&#123;callback_token&#125;&#125;` | Token for unauthenticated resolution                      |
| `&#123;&#123;timestamp&#125;&#125;`      | ISO timestamp of the event                                |
| `&#123;&#123;fields.*&#125;&#125;`       | Form field values (for user input requests)               |
| `&#123;&#123;plan.*&#125;&#125;`         | Plan details (for plan approvals)                         |
| `&#123;&#123;event.*&#125;&#125;`        | Raw event data fields                                     |

### Template Resolution

When a notification needs to be sent, the Template Registry resolves the template using a fallback chain:

1. **Exact match**: `(tenant_id, event_type, channel_type)` — tenant has a custom template for this specific event and channel
2. **Category fallback**: `(tenant_id, event_category.*, channel_type)` — tenant has a template for the event category (e.g., `guard.*` catches all guard events)
3. **Default fallback**: `(tenant_id, *, channel_type)` — tenant's catch-all template for the channel
4. **Master fallback**: If no tenant template matches, use the root tenant's template (the master)

This means a tenant can override just the email template for approvals while using defaults for everything else.

### Editing Templates

**Dashboard**: Navigate to **Settings → Channels → Templates**. Select an event type and channel to view and edit the template. A preview pane shows the rendered output with sample data.

**API**:

```bash theme={null}
# List all templates for the tenant
POST /internal/templates/list

# Get a specific template
POST /internal/templates/get
{ "event_type": "guard.approval_required", "channel_type": "email" }

# Update a template (creates tenant override if editing a default)
POST /internal/templates/update
{
  "event_type": "guard.approval_required",
  "channel_type": "email",
  "subject": "{{severity}} — Approval needed: {{title}}",
  "html_body": "<div>... custom HTML ...</div>"
}

# Reset to default (deletes tenant override, reverts to master)
POST /internal/templates/reset
{ "event_type": "guard.approval_required", "channel_type": "email" }
```

***

## Default Event-to-Channel Mappings

Every new tenant receives the following default notification mappings. These determine which channels are notified for which events:

| Event                                 | In-App | Email | Slack | Webhook | SMS | Phone |
| ------------------------------------- | ------ | ----- | ----- | ------- | --- | ----- |
| **guard.approval\_required**          | Yes    | Yes   | Yes   | Yes     | No  | No    |
| **policy.approval\_required**         | Yes    | Yes   | Yes   | Yes     | No  | No    |
| **agent.health\_changed** (unhealthy) | Yes    | Yes   | Yes   | Yes     | No  | No    |
| **agent.deregistered**                | Yes    | Yes   | Yes   | Yes     | No  | No    |
| **dispatch.failed** (threshold)       | Yes    | Yes   | Yes   | Yes     | No  | No    |
| **message.error**                     | Yes    | No    | No    | No      | No  | No    |
| **message.completed**                 | No     | No    | No    | Yes     | No  | No    |
| **agent.registered**                  | Yes    | No    | No    | Yes     | No  | No    |
| **session.started**                   | No     | No    | No    | Yes     | No  | No    |

**SMS and phone are never enabled by default** — they are opt-in per policy because they are intrusive and incur per-message/per-minute costs.

Mappings are managed through policies. Each policy can link to any combination of channels. The table above reflects the built-in policies that ship with every tenant.

***

## Default Templates by Event Type

### Approval Required

| Channel     | Content                                                                                                                                                                                        |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Email**   | Subject: "Action Required: \{\{title}}". HTML body with description, form fields (non-sensitive), and "Review in Dashboard" button. Sensitive fields show a warning and redirect to dashboard. |
| **Slack**   | Header block with title. Description section. Approve/Deny buttons for simple approvals, or "Complete in Dashboard" button for complex forms.                                                  |
| **Webhook** | JSON with `type: "approval_required"`, A2UI spec, callback\_token, resolve\_url.                                                                                                               |
| **SMS**     | "Svantic: Approval needed — \{\{title}}. Reply APPROVE \{\{ref}} or DENY \{\{ref}}. Details: \{\{dashboard\_url}}"                                                                             |
| **Phone**   | TTS: "This is Svantic. \{\{agent\_type}} needs approval for \{\{title}}. Press 1 to approve. Press 2 to deny." DTMF: 1=approve, 2=deny.                                                        |

### Alert (Agent Unhealthy, Dispatch Failed, etc.)

| Channel     | Content                                                                                                                                 |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **Email**   | Subject: "Alert: \{\{rule\_name}}". HTML body with severity indicator, event details table (key-value), and "View in Dashboard" button. |
| **Slack**   | Severity emoji header (red=critical, yellow=warning, blue=info). Event summary fields. Rule name and severity context line.             |
| **Webhook** | JSON with `type: "alert"`, full event data, severity, rule\_name.                                                                       |
| **SMS**     | "Svantic \{\{severity}}: \{\{rule\_name}} — \{\{title}}. View: \{\{dashboard\_url}}"                                                    |
| **Phone**   | TTS: "This is Svantic. \{\{severity}} alert: \{\{rule\_name}}. \{\{title}}." (No DTMF — informational only.)                            |

### Operation Complete

| Channel     | Content                                                                             |
| ----------- | ----------------------------------------------------------------------------------- |
| **Email**   | Subject: "Completed: \{\{title}}". HTML body with summary, duration, link to trace. |
| **Slack**   | Checkmark header. Operation summary. Link to trace in dashboard.                    |
| **Webhook** | JSON with `type: "operation_complete"`, result data, trace\_id, duration\_ms.       |
| **SMS**     | "Svantic: \{\{title}} completed. View: \{\{dashboard\_url}}"                        |
| **Phone**   | Not used for operation complete (not time-sensitive).                               |

### Informational (Agent Registered, Session Started, etc.)

| Channel            | Content                                                          |
| ------------------ | ---------------------------------------------------------------- |
| **Webhook**        | JSON with `type: "informational"`, event data.                   |
| **Other channels** | Not delivered by default (too noisy). Can be enabled per policy. |

***

## Creating Channels

### Dashboard

Navigate to **Settings → Channels** and click **New Channel**. Select the channel type, provide a name and configuration, and save.

### API

```bash theme={null}
POST /internal/channels/new
{
  "type": "slack",
  "name": "Engineering alerts",
  "config": {
    "bot_token": "xoxb-...",
    "channel_id": "C0123456789"
  }
}
```

### Default Channels

Every new tenant is provisioned with four default channels:

| Channel       | Default State     | Ready to Use?                                         |
| ------------- | ----------------- | ----------------------------------------------------- |
| **Email**     | Enabled           | Yes — uses platform SMTP, sends to tenant admin email |
| **Slack**     | Created, disabled | No — needs bot\_token and channel\_id                 |
| **Webhook**   | Created, disabled | No — needs target URL                                 |
| **Phone/SMS** | Created, disabled | No — needs Twilio credentials                         |

The email channel works immediately — no configuration needed beyond the tenant's admin email address. Other channels require provider credentials before they can deliver.

***

## Linking Channels to Policies

Channels are linked to policies to control which notifications go where:

```bash theme={null}
POST /internal/policies/update
{
  "policy_id": "policy-uuid",
  "channels": [
    { "channel_id": "slack-channel-uuid", "enabled": true },
    { "channel_id": "email-channel-uuid", "enabled": true },
    { "channel_id": "phone-channel-uuid", "enabled": false }
  ]
}
```

A single channel can be linked to multiple policies. Individual links can be enabled/disabled without removing the channel. This allows fine-grained control — for example, send all alerts to Slack, but only send critical approvals to the phone channel.

***

## Delivery Tracking

Every notification delivery is tracked:

| Field              | Description                                                  |
| ------------------ | ------------------------------------------------------------ |
| `channel_id`       | Which channel was used                                       |
| `status_code`      | HTTP status for webhook/Slack, delivery status for email/SMS |
| `response_time_ms` | Delivery latency                                             |
| `success`          | Whether the delivery succeeded                               |
| `error`            | Error message if failed                                      |
| `delivered_at`     | Timestamp                                                    |

View delivery history in **Settings → Channels → (select channel) → Delivery Log**, or via the API:

```bash theme={null}
POST /internal/channels/deliveries
{ "channel_id": "uuid", "page": 1, "per_page": 20 }
```

***

## Testing Channels

Send a test notification through any configured channel:

```bash theme={null}
POST /internal/policies/test
{ "policy_id": "policy-uuid" }
```

This sends a sample notification through all linked channels without triggering any real enforcement. Use this to verify delivery configuration before going live.

***

## API Reference

| Endpoint                             | Description                                  |
| ------------------------------------ | -------------------------------------------- |
| `POST /internal/channels/get`        | List all channels for the tenant             |
| `POST /internal/channels/new`        | Create a new channel                         |
| `POST /internal/channels/update`     | Update channel config or enabled state       |
| `POST /internal/channels/delete`     | Delete a channel (unlinks from all policies) |
| `POST /internal/channels/deliveries` | Delivery log for a channel                   |
| `POST /internal/templates/list`      | List all templates for the tenant            |
| `POST /internal/templates/get`       | Get a specific template                      |
| `POST /internal/templates/update`    | Create or update a tenant template override  |
| `POST /internal/templates/reset`     | Reset a template to the master default       |

***

## Further Reading

* [Long-Running Operations](./long-running-operations) — how agents pause and resume
* [Approvals Guide](./approvals) — step-by-step approval walkthrough
* [Policies Guide](./policies) — creating rules that trigger notifications
* [Slack Integration](./slack-integration) — detailed Slack setup instructions
