Notification Templates
Templates let modules define reusable notification content once and
send it with variable substitution on demand. A single template can
target multiple channels simultaneously — one sendFromTemplate()
call sends an email and an in-app WebSocket notification at the same
time.
Create a Template
POST https://api.septemcore.com/v1/notifications/templates
Authorization: Bearer <access_token>
Content-Type: application/json
{
"name": "invoice-ready",
"description": "Sent when an invoice is generated for a customer",
"channels": [
{
"channel": "email",
"subject": "Invoice #{{invoiceNumber}} is ready",
"body": "Hi {{name}},\n\nYour invoice #{{invoiceNumber}} for {{amount}} is ready.\nDue date: {{dueDate}}.\n\nDownload: {{downloadUrl}}"
},
{
"channel": "websocket",
"body": "Invoice #{{invoiceNumber}} ready — {{amount}} due {{dueDate}}"
}
]
}
Response 201 Created:
{
"templateId": "01j9patpl900000000000000",
"name": "invoice-ready",
"channels": ["email", "websocket"],
"createdAt": "2026-04-15T10:30:00.000Z",
"version": 1
}
Variable Syntax
Template bodies use {{variableName}} placeholders. Variables are
replaced at send time from the variables map passed to
sendFromTemplate(). Unresolved variables (present in template but
absent from the variables map) are replaced with an empty string
and the notification is still sent — no error is returned.
Template body: "Hi {{name}}, your balance is {{balance}}."
Variables: { "name": "Alice" }
Rendered: "Hi Alice, your balance is ."
For required variables, validate their presence in your module code
before calling sendFromTemplate().
SDK — sendFromTemplate()
import { kernel } from '@platform/sdk-core';
await kernel.notify().sendFromTemplate({
templateId: '01j9patpl900000000000000',
userId: '01j9pa5mz700000000000000',
variables: {
name: 'Alice Chen',
invoiceNumber: '1042',
amount: '$250.00',
dueDate: '2026-04-30',
downloadUrl: 'https://app.acme.com/invoices/1042',
},
priority: 'normal',
});
The SDK resolves which channels the template targets, renders each channel's body with the provided variables, and dispatches one notification per channel — all in a single call.
REST — sendFromTemplate()
POST https://api.septemcore.com/v1/notifications
Authorization: Bearer <access_token>
Content-Type: application/json
{
"templateId": "01j9patpl900000000000000",
"userId": "01j9pa5mz700000000000000",
"variables": {
"name": "Alice Chen",
"invoiceNumber": "1042",
"amount": "$250.00",
"dueDate": "2026-04-30",
"downloadUrl": "https://app.acme.com/invoices/1042"
},
"priority": "normal"
}
Response 202 Accepted:
{
"notifications": [
{ "notificationId": "01j9panot700000000000000", "channel": "email", "status": "queued" },
{ "notificationId": "01j9panot800000000000000", "channel": "websocket", "status": "queued" }
]
}
One notificationId is returned per channel in the template. Each
can be tracked independently.
Multiplex: One Template, Multiple Channels
A template with two or more channels in its channels array delivers
to all of them in a single sendFromTemplate() call:
Template "invoice-ready":
channels: ["email", "websocket"]
sendFromTemplate({ templateId: 'invoice-ready', userId: '...', variables: {...} })
│
├──▶ email: Renders email body → enqueues to RabbitMQ → SendGrid
└──▶ websocket: Renders WebSocket body → pushes to browser connection
Each channel delivery is independent: if the email adapter is down and retrying, the WebSocket notification is delivered immediately. One channel's failure never blocks another channel.
List Templates
GET https://api.septemcore.com/v1/notifications/templates
Authorization: Bearer <access_token>
{
"data": [
{
"templateId": "01j9patpl900000000000000",
"name": "invoice-ready",
"channels": ["email", "websocket"],
"version": 1,
"createdAt": "2026-04-15T10:30:00.000Z"
},
{
"templateId": "01j9patpl910000000000000",
"name": "welcome",
"channels": ["email"],
"version": 2,
"createdAt": "2026-04-10T08:00:00.000Z"
}
],
"pagination": {
"nextCursor": null,
"hasMore": false
}
}
Retrieve a Template
GET https://api.septemcore.com/v1/notifications/templates/01j9patpl900000000000000
Authorization: Bearer <access_token>
{
"templateId": "01j9patpl900000000000000",
"name": "invoice-ready",
"description": "Sent when an invoice is generated for a customer",
"version": 1,
"channels": [
{
"channel": "email",
"subject": "Invoice #{{invoiceNumber}} is ready",
"body": "Hi {{name}},\n\nYour invoice #{{invoiceNumber}} for {{amount}} is ready.\nDue date: {{dueDate}}."
},
{
"channel": "websocket",
"body": "Invoice #{{invoiceNumber}} ready — {{amount}} due {{dueDate}}"
}
],
"createdAt": "2026-04-15T10:30:00.000Z"
}
Update a Template
PUT https://api.septemcore.com/v1/notifications/templates/01j9patpl900000000000000
Authorization: Bearer <access_token>
Content-Type: application/json
{
"channels": [
{
"channel": "email",
"subject": "Invoice #{{invoiceNumber}} is ready — action required",
"body": "Hi {{name}},\n\nYour invoice #{{invoiceNumber}} for {{amount}} is due on {{dueDate}}."
}
]
}
Each PUT increments version. The version is stored for audit
purposes — the Audit Service records which template version was used
for each notification send.
Delete a Template
DELETE https://api.septemcore.com/v1/notifications/templates/01j9patpl900000000000000
Authorization: Bearer <access_token>
Response 204 No Content. Deleting a template does not affect
historical notifications that referenced it — the rendered body is
stored in the notification record at send time.
Error Reference
| Scenario | HTTP | Code |
|---|---|---|
templateId not found | 404 | not-found |
| Template references unregistered channel | 422 | CHANNEL_NOT_FOUND |
templateId and body both in request | 400 | validation-error |
| Template name already exists | 409 | TEMPLATE_NAME_CONFLICT |