Skip to main content

Personalisation

Campaign copy can contain placeholders that the worker fills per device, after the locale is picked and before the transport is called:

Hey {{firstName|there}}, your {{streak}}-day streak ends tonight.

Syntax

FormResult when the value existsWhen it does not
{{name}}the valueempty string
`{{namefallback}}`the value
  • Names: letters, digits, _, ., -, up to 64 chars. Whitespace inside the braces is ignored ({{ name }}).
  • Fallbacks: up to 64 chars, no } or |.
  • After substitution, runs of spaces are collapsed and the string is trimmed, so a missing value without a fallback leaves Hey , welcome as Hey , welcome — write a fallback for anything that can be absent.
  • Works in title, body and url. Not in data.

Values

SourceNames
The subscriber's tagsevery key: {{firstName}}, {{plan}}, {{cartItems}}
Built-ins{{externalId}}, {{country}}, {{language}}, {{platform}}

Numbers and booleans are stringified (42, true). Built-ins win over a tag of the same name.

Not a template language

On purpose there are no expressions, conditionals, loops, filters or nested access. Copy is written by operators and rendered on devices you cannot debug; the failure mode of a real template engine is a push that says {{#if}} to ten thousand people. If you need logic, compute the value in your backend and store it as a tag ({{greeting}}).

Preview

The wizard's Preview step runs the exact same function the worker uses (core/personalize.ts), with sample values you type. The Variables panel on the content step lists the tag keys your audience actually has, so a typo in a placeholder is visible before sending.

API example

{
"target": { "externalIds": ["user_123"] },
"content": {
"en": { "title": "{{firstName|Hey}}, {{cartItems}} items are waiting", "body": "Finish checkout before they sell out.", "url": "acme://cart?src={{platform}}" },
"_default": "en"
}
}

A user with tags { firstName: "Ayşe", cartItems: 3 } on iOS receives Ayşe, 3 items are waiting and acme://cart?src=ios.