Templates¶
A template holds the content of a message in one or more locales.
{
"key": "payment-due",
"default_locale": "en",
"locales": {
"en": {
"title": "Payment due {{ data.due_date }}",
"body": "{{ data.amount | number }} {{ data.currency }} is due. Tap to pay.",
"url": "myapp://invoices/{{ data.invoice_id }}",
"image_url": null,
"data": {"screen": "invoice", "invoice_id": "{{ data.invoice_id }}"},
"channels": {
"sms": {"body": "{{ data.amount | number }} {{ data.currency }} due {{ data.due_date }}."},
"email": {"html": "<p>Your invoice <b>{{ data.invoice_id }}</b> is due.</p>"}
}
},
"ru": {"title": "…", "body": "…"}
},
"ai_instructions": null,
"ai_attributes": []
}
Fields¶
| Field | Used by |
|---|---|
title |
Push title, e-mail subject, bold first line in Telegram. |
body |
Every channel. |
url |
Deep link: push data.url, appended to SMS/Telegram, link in e-mail text. |
image_url |
Push notification image. |
thread |
Groups related messages on the device: APNs thread-id, FCM data.thread, an e-mail References header, forwarded by webhooks. See digests. |
html |
E-mail HTML part (auto-escaped). |
data |
Push data payload / webhook payload. Values are templates too. |
actions |
Up to four buttons (id, label, url); see importance and expiry. |
channels |
Per-channel overrides. Unset fields inherit from the locale's base content. |
Variables¶
| Variable | Contents |
|---|---|
data |
The event's data, the broadcast's variables, or the direct send's data. |
recipient |
external_id, locale, timezone, attributes. |
event |
name, source, occurred_at — null for direct sends and broadcasts. |
category |
The message category key. |
importance |
low, normal, high or critical. |
links |
preferences and unsubscribe URLs and the preference token — see preference centre. |
Filters: everything Jinja2 provides
plus number(decimals=0, separator=" ", point=".") for thousands separators:
{{ 1250000 | number }} → 1 250 000.
Safety and strictness¶
- Templates run in Jinja's immutable sandbox: no access to Python internals, no mutation of data.
- Undefined variables are errors, not empty strings. A message that would read
"Your order has shipped" fails with
status_reason: template_error: …instead. Use{{ data.name | default("there") }}for optional values. - Syntax is checked when the template is saved.
htmlis auto-escaped; other fields are plain text.- Resource limits stop runaway templates:
range()is capped at 1 000 items, powers at an exponent of 64, sequence repetition at 10 000 items,center/indentwidths at 200 and each rendered field at 20 000 characters. Python string formatting (%,.format, theformatfilter) is disabled — use~to concatenate.
Locale selection¶
For a recipient with locale uz-Latn-UZ, Cue tries uz-latn-uz, uz-latn, uz,
then the template's default_locale, then any available locale. Matching is
case-insensitive and treats _ like -.
Versions and previews¶
Every content change increments version; messages record the template version they were
rendered from. POST /v1/templates/{key}/preview renders all variants with sample data
(and optionally a real recipient) without sending anything.