Skip to content

Commit 3671359

Browse files
authored
Fixed relative urls in member welcome emails (#26693)
ref https://linear.app/ghost/issue/NY-1127/ - made urls full instead of relative, which won't work in email
1 parent be8c65e commit 3671359

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

ghost/core/core/server/services/member-welcome-emails/member-welcome-email-renderer.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const errors = require('@tryghost/errors');
77
const {textColorForBackgroundColor} = require('@tryghost/color-utils');
88
const {MESSAGES} = require('./constants');
99
const {wrapReplacementStrings} = require('../koenig/render-utils/replacement-strings');
10+
const linkReplacer = require('../lib/link-replacer');
1011

1112
const REPLACEMENT_REGEX = /%%\{(\w+?)(?:,? *"(.*?)")?\}%%/g;
1213
const UNMATCHED_TOKEN_REGEX = /%%\{.*?\}%%/g;
@@ -116,13 +117,18 @@ class MemberWelcomeEmailRenderer {
116117
const contentWithReplacements = this.#applyReplacements({definitions, text: content, escapeHtml: true});
117118
const subjectWithReplacements = this.#applyReplacements({definitions, text: subject, escapeHtml: false});
118119

120+
// Resolve relative links (e.g. #/portal/signup) to absolute URLs using the site URL
121+
const contentWithAbsoluteLinks = await linkReplacer.replace(contentWithReplacements, (url) => {
122+
return url;
123+
}, {base: siteSettings.url});
124+
119125
const managePreferencesUrl = new URL('#/portal/account/newsletters', siteSettings.url).href;
120126
const year = new Date().getFullYear();
121127
const accentColor = siteSettings.accentColor || '#15212A';
122128
const accentContrastColor = textColorForBackgroundColor(accentColor).hex();
123129

124130
const html = this.#wrapperTemplate({
125-
content: contentWithReplacements,
131+
content: contentWithAbsoluteLinks,
126132
subject: subjectWithReplacements,
127133
siteTitle: siteSettings.title,
128134
siteUrl: siteSettings.url,

ghost/core/test/unit/server/services/member-welcome-emails/member-welcome-email-renderer.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,21 @@ describe('MemberWelcomeEmailRenderer', function () {
228228
assert(result.html.includes('https://example.com/#/portal/account'));
229229
});
230230

231+
it('resolves relative portal links to absolute URLs', async function () {
232+
lexicalRenderStub.resolves('<table class="kg-card kg-button-card"><tbody><tr><td><table class="btn"><tbody><tr><td align="center"><a href="#/portal/support">Support us</a></td></tr></tbody></table></td></tr></tbody></table>');
233+
const renderer = new MemberWelcomeEmailRenderer({t: key => key});
234+
235+
const result = await renderer.render({
236+
lexical: '{}',
237+
subject: 'Welcome!',
238+
member: {name: 'John', email: 'john@example.com'},
239+
siteSettings: defaultSiteSettings
240+
});
241+
242+
assert(result.html.includes('https://example.com/#/portal/support'));
243+
assert(!result.html.match(/href="[^"]*"[^>]*>[^<]*Support us/).toString().includes('href="#/portal/support"'));
244+
});
245+
231246
it('generates plain text from HTML', async function () {
232247
lexicalRenderStub.resolves('<p>Hello World</p>');
233248
const renderer = new MemberWelcomeEmailRenderer({t: key => key});

0 commit comments

Comments
 (0)