From ee8360585bccc4cdb4f736d5bb8c69d5c5d61b17 Mon Sep 17 00:00:00 2001 From: Arunendra Tripathi <156455722+Arunendra21@users.noreply.github.com> Date: Wed, 26 Aug 2026 07:27:35 +0530 Subject: [PATCH] fix: render adjacent email template variables correctly (#3153) --- .../render-custom-email-template.test.ts | 32 +++++++++++++++++++ .../lib/utils/render-custom-email-template.ts | 6 +++- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 packages/lib/utils/render-custom-email-template.test.ts diff --git a/packages/lib/utils/render-custom-email-template.test.ts b/packages/lib/utils/render-custom-email-template.test.ts new file mode 100644 index 000000000..b1bd4d2c7 --- /dev/null +++ b/packages/lib/utils/render-custom-email-template.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; + +import { renderCustomEmailTemplate } from './render-custom-email-template'; + +describe('renderCustomEmailTemplate', () => { + it('replaces a single variable', () => { + expect(renderCustomEmailTemplate('Hi {name}', { name: 'Sam' })).toBe('Hi Sam'); + }); + + it('replaces multiple variables separated by whitespace', () => { + expect( + renderCustomEmailTemplate('Hi {name}, sign at {url}', { name: 'Sam', url: 'https://x' }), + ).toBe('Hi Sam, sign at https://x'); + }); + + it('replaces adjacent variables and variables separated by a non-whitespace character', () => { + expect( + renderCustomEmailTemplate('{day}/{month}/{year}', { day: '01', month: '02', year: '2026' }), + ).toBe('01/02/2026'); + + expect( + renderCustomEmailTemplate('{firstName}-{lastName}', { + firstName: 'Ada', + lastName: 'Lovelace', + }), + ).toBe('Ada-Lovelace'); + }); + + it('leaves the key in place when the variable is not provided', () => { + expect(renderCustomEmailTemplate('Unknown {missing} here', {})).toBe('Unknown missing here'); + }); +}); diff --git a/packages/lib/utils/render-custom-email-template.ts b/packages/lib/utils/render-custom-email-template.ts index dbe7ae621..c1201bbd5 100644 --- a/packages/lib/utils/render-custom-email-template.ts +++ b/packages/lib/utils/render-custom-email-template.ts @@ -1,5 +1,9 @@ export const renderCustomEmailTemplate = >(template: string, variables: T): string => { - return template.replace(/\{(\S+)\}/g, (_, key) => { + // Match a run of non-brace, non-whitespace characters between braces. Using + // `\S+` here was greedy across braces, so placeholders separated by a + // non-whitespace character (e.g. "{day}/{month}/{year}") were captured as a + // single bogus key and left unrendered. + return template.replace(/\{([^\s{}]+)\}/g, (_, key) => { if (key in variables) { return variables[key]; }