Skip to main content

For admins

Advanced: Journeys tokens in design systems

For design-system developers writing Customer Insights - Journeys tokens directly into module code. Recipient fields, trigger fields and conditions need none of this; see Personalization and segmentation.

Better Email's design systems use Liquid, and so {{...}} in module code is evaluated by Liquid. Wrap any Journeys token in {% raw %}...{% endraw %} so it reaches Journeys as written.

Journeys warns when an email lacks the company address and preference center tokens. Put them in the footer module:

{% raw %}{{CompanyAddress}}{% endraw %}
{% raw %}{{PreferenceCenter}}{% endraw %}

Better Email writes their definitions onto the email on sync, so the designer fills them in from your compliance settings.

Lists and hand-written placeholders​

Most personalization needs none of this. Profile fields and trigger fields come from the field sync, and marketers insert them in the Campaign Editor. This section is for what the sync doesn't cover, mainly a list: each recipient's related records, such as upcoming appointments. A module can declare such a placeholder itself, so every email built with it arrives in Journeys already bound.

Put a comment carrying the placeholder definition in front of the token, inside the same {% raw %} block:

{% raw %}<!--be:dynamics-placeholder:{
"name": "Appointments",
"definition": { ... }
}-->{{#each Appointments}}{{Appointments.Description}}{{/each}}{% endraw %}

Replace { ... } with a real definition, copied as described below.

On sync, Better Email removes the comment and writes definition, exactly as written, onto the email under name. name takes letters, digits and underscores, and can't start with a digit. A comment that isn't valid JSON, or breaks that rule, is dropped. Journeys validates the definition itself in Check content.

The easiest way to get a definition right is to copy one Journeys wrote: bind the token once in the designer, then read the email's msdynmkt_placeholders column (a JSON object keyed by placeholder name), for example with GET <organization URL>/api/data/v9.2/msdynmkt_emails(<email id>)?$select=msdynmkt_placeholders. For a list, that's "category": "list" with source DataverseDataSource, a relationshipName, relatingRecordId bound to the recipient, and topCount, orderBy and outputs.

The same comment works for a trigger attribute the field sync doesn't offer: source EventDataSource, sourceType the trigger's logical name, outputPath the attribute. Journeys only resolves it in journeys started by that trigger, and Better Email doesn't check that for you, so prefer a trigger field when there is one.

Declared tokens count as personalization in pre-flight checks when the Campaign syncs to Journeys. Only a Journeys sync removes the comment, so use such modules only in design systems that sync to Journeys. A declaration wins over a binding made in the designer, so change it in the module.

Personalization bound in the Journeys designer​

You can also bind a token in the designer's Personalize pane after the first sync, which is how the condition behind a code segment is defined. Later syncs keep a designer binding as long as the email still uses the name. It lives on that one email, so another Campaign needs it bound again.

A plain token such as {{ArrivalDate}} bound only in the designer still shows up in pre-flight checks as an unrendered merge tag. Use a trigger field, or declare a list in the module.