Support all Personalization tokens in In-app
Issue
The UI escapes editable attributes to build valid HTML for the preview, e.g.
<p me-copy-to-clipboard="&"<>"></p>
But the Personalization API does not support HTML encoding. Inapp service has to transform the HTML before sending it to personalization.
Solution
Email team had the same issue and shared their solution with us. They replace each personalization token with the preview in the base64 encoded string, e.g.
Personalization token:
{# pers-token:1 eyJ0b2tlbk5hbWUiOiJEaW5rbyAtIFZvdWNoZXIiLCJ0eXBlIjoicGVyc29uYWxpemF0aW9uIiwidG9rZW4iOnsibmFtZSI6IkRpbmtvIC0gVm91Y2hlciIsInNvdXJjZSI6InZvdWNoZXIiLCJjb250ZW50Ijp7ImZpZWxkIjoiNzI5In0sImNvZGUiOiIiLCJmaWx0ZXJzIjp7ImZhbGxiYWNrIjoiZGVmYXVsdCB2b3VjaGVyIiwibW9kaWZpZXIiOiIiLCJyZXF1aXJlZCI6ZmFsc2UsImluZGV4IjowfSwiY3JlYXRlZF9hdCI6IjIwMjUtMDMtMTNUMjA6NDk6MTIuMjkwWiIsInVwZGF0ZWRfYXQiOiIyMDI1LTAzLTEzVDIwOjUyOjE1LjM5NVoifSwicHJldmlldyI6Int7IHZvdWNoZXIuNzI5IHwgZGVmYXVsdCgnZGVmYXVsdCB2b3VjaGVyJykgfX0ifQ== #}{{ voucher.729 | default('default voucher') }}{# pers-token:1 #}
Base64 decoded:
{
"tokenName": "Dinko - Voucher",
"type": "personalization",
"token": {
"name": "Dinko - Voucher",
"source": "voucher",
"content": {
"field": "729"
},
"code": "",
"filters": {
"fallback": "default voucher",
"modifier": "",
"required": false,
"index": 0
},
"created_at": "2025-03-13T20:49:12.290Z",
"updated_at": "2025-03-13T20:52:15.395Z"
},
"preview": "{{ voucher.729 | default('default voucher') }}"
}
Preview:
{{ voucher.729 | default('default voucher') }}
Because the base64-encoded part is used, inapp service removes all HTML escaping done by the UI.
Note: It can happen that inapp service sends an invalid HTML to personalization API. But that’s no issue because personalization is not aware of HTML.
Serving Valid HTML
When a personalization token is used in an HTML attribute and the output has a double quote in it, then DES would serve invalid HTML, e.g.
Input:
<div me-copy-to-clipboard="{{ a | default('"') }}"></div>
Output:
<div me-copy-to-clipboard="""></div>
Inapp service can use autoescape to avoid that:
{% autoescape 'html' %}{{ a | default('&\"<>') }}{% endautoescape %}
When replacing the personalization tokens with the preview of the base64-encoded string inapp service can wrap it in autoescape.
Implementation
Inapp service can use a similar version as replaceV3ToTwig with the additional autoescape.
replaceV3ToTwig uses findPlaceholderPositions function from the personalization-twig-token repo which inapp can use as dependency.