From 00b577385d095208ddcde145d5e5ab43a7ee7364 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 08:38:36 +0000 Subject: [PATCH] fix(messaging): stop merge tags inserting double quotes The email templater menu built every person-property merge tag as {{person.properties["name"]}}. The double quotes come from us, not the user. Inside an HTML attribute like a link href, a double quote ends the attribute and mangles the tag. When the quotes survive as an HTML entity, the renderer resolves the tag to an empty string and the email sends a blank value with no error. Emit dot notation for identifier-safe names and single-quoted brackets for everything else, matching buildDelayExpression in the workflows delay step. Generated-By: PostHog Desktop Task-Id: 4b35bbb8-8157-4e92-86f5-f619d4294408 --- .../email-templater/emailTemplaterLogic.test.ts | 16 ++++++++++++++++ .../email-templater/emailTemplaterLogic.tsx | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.test.ts b/frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.test.ts index bb5d615b3d4d..596b462ee5c2 100644 --- a/frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.test.ts +++ b/frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.test.ts @@ -9,6 +9,7 @@ import { EditorRef, EmailTemplate, EmailTemplaterLogicProps, + buildPersonPropertyMergeValue, emailTemplaterLogic, } from './emailTemplaterLogic' @@ -622,3 +623,18 @@ describe('emailTemplaterLogic', () => { }) }) }) + +describe('buildPersonPropertyMergeValue', () => { + // Never emit double quotes: they end an HTML attribute like a link href, and entity-encoded they + // render an empty value. A bare identifier uses dot access; everything else uses single-quoted + // brackets. + it.each([ + ['first_name', '{{person.properties.first_name}}'], + ['$browser', "{{person.properties['$browser']}}"], + ['renews on', "{{person.properties['renews on']}}"], + ['a.b', "{{person.properties['a.b']}}"], + ["it's", "{{person.properties['it\\'s']}}"], + ])('builds %s as %s', (name, expected) => { + expect(buildPersonPropertyMergeValue(name)).toBe(expected) + }) +}) diff --git a/frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.tsx b/frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.tsx index ea337d437788..b4eacbb71a08 100644 --- a/frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.tsx +++ b/frontend/src/scenes/hog-functions/email-templater/emailTemplaterLogic.tsx @@ -151,6 +151,21 @@ export function buildHtmlWrapDesign(html: string): JSONTemplate { const EMAIL_EDITOR_URL_PARAM = 'editor' const EMAIL_EDITOR_URL_VALUE = 'email' +// A property name reads back as `person.properties.foo` only when it is a bare identifier. Anything +// else (spaces, a leading $, punctuation) needs bracket access. Use single quotes, never double: a +// double quote inside an HTML attribute like a link href ends the attribute and breaks the tag, and +// when it survives as an HTML entity the renderer resolves the tag to an empty string. This mirrors +// buildDelayExpression in products/workflows stepDelayLogic. +const BARE_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/ + +export function buildPersonPropertyMergeValue(name: string): string { + if (BARE_IDENTIFIER_REGEX.test(name)) { + return `{{person.properties.${name}}}` + } + const escaped = name.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + return `{{person.properties['${escaped}']}}` +} + export interface EmailTemplaterLogicProps { value: EmailTemplate | null onChange: (value: EmailTemplate) => void @@ -499,7 +514,7 @@ export const emailTemplaterLogic = kea([ personPropertyDefinitions.forEach((property: PropertyDefinition) => { tags[property.name] = { name: property.name, - value: `{{person.properties["${property.name}"]}}`, + value: buildPersonPropertyMergeValue(property.name), sample: property.example || `Sample ${property.name}`, } })