From 6d8e4e4c5b8a15294b0b3ba1ecfbfafab6c84336 Mon Sep 17 00:00:00 2001 From: Kristina <7614497+khjrtbrg@users.noreply.github.com> Date: Mon, 8 Jun 2026 11:18:18 +0200 Subject: [PATCH] [web] ship updated email templates (#34180) GitOrigin-RevId: fa04ff132ec898efdb4e2d35df8b8a0b09219d5e --- .../Features/Email/Bodies/NoCTAEmailBody.mjs | 62 +-- .../Email/Bodies/UpdatedNoCTAEmailBody.mjs | 28 - .../src/Features/Email/Bodies/cta-email.mjs | 141 ++--- .../Email/Bodies/updated-cta-email.mjs | 65 --- .../app/src/Features/Email/EmailBuilder.mjs | 21 +- .../app/src/Features/Email/EmailHandler.mjs | 23 - .../Email/Layouts/BaseEmailLayout.mjs | 524 +++++------------- .../Email/Layouts/UpdatedBaseEmailLayout.mjs | 169 ------ ...yout.test.mjs => BaseEmailLayout.test.mjs} | 4 +- .../test/unit/src/Email/EmailBuilder.test.mjs | 21 - 10 files changed, 232 insertions(+), 826 deletions(-) delete mode 100644 services/web/app/src/Features/Email/Bodies/UpdatedNoCTAEmailBody.mjs delete mode 100644 services/web/app/src/Features/Email/Bodies/updated-cta-email.mjs delete mode 100644 services/web/app/src/Features/Email/Layouts/UpdatedBaseEmailLayout.mjs rename services/web/test/unit/src/Email/{UpdatedBaseEmailLayout.test.mjs => BaseEmailLayout.test.mjs} (94%) diff --git a/services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.mjs b/services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.mjs index a078cad8e6..6a314c2926 100644 --- a/services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.mjs +++ b/services/web/app/src/Features/Email/Bodies/NoCTAEmailBody.mjs @@ -1,46 +1,28 @@ import _ from 'lodash' +import { fontFamily, colors } from '../emailStyles.mjs' export default _.template(`\ - - - - - - -
- - - - - - - -
- <% if (title) { %> -

- <%= title %> -

- <% } %> -
-

 

+<% if (title) { %> +

+ <%= title %> +

+<% } %> - <% if (greeting) { %> -

- <%= greeting %> -

- <% } %> +<% if (greeting) { %> +

+ <%= greeting %> +

+<% } %> - <% (message).forEach(function(paragraph) { %> -

- <%= paragraph %> -

- <% }) %> - <% if (highlightedText) { %> -
- <%= highlightedText %> -
- <% } %> -
-
-\ +<% (message).forEach(function(paragraph) { %> +

+ <%= paragraph %> +

+<% }) %> + +<% if (highlightedText) { %> +
+ <%= highlightedText %> +
+<% } %>\ `) diff --git a/services/web/app/src/Features/Email/Bodies/UpdatedNoCTAEmailBody.mjs b/services/web/app/src/Features/Email/Bodies/UpdatedNoCTAEmailBody.mjs deleted file mode 100644 index 6a314c2926..0000000000 --- a/services/web/app/src/Features/Email/Bodies/UpdatedNoCTAEmailBody.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import _ from 'lodash' -import { fontFamily, colors } from '../emailStyles.mjs' - -export default _.template(`\ -<% if (title) { %> -

- <%= title %> -

-<% } %> - -<% if (greeting) { %> -

- <%= greeting %> -

-<% } %> - -<% (message).forEach(function(paragraph) { %> -

- <%= paragraph %> -

-<% }) %> - -<% if (highlightedText) { %> -
- <%= highlightedText %> -
-<% } %>\ -`) diff --git a/services/web/app/src/Features/Email/Bodies/cta-email.mjs b/services/web/app/src/Features/Email/Bodies/cta-email.mjs index 6d49620a7d..8987b62251 100644 --- a/services/web/app/src/Features/Email/Bodies/cta-email.mjs +++ b/services/web/app/src/Features/Email/Bodies/cta-email.mjs @@ -1,96 +1,65 @@ import _ from 'lodash' +import { fontFamily, colors } from '../emailStyles.mjs' export default _.template(`\ - - - - - - -
- - - - - - - -
- <% if (title) { %> -

- <%= title %> -

- <% } %> -
-

 

+<% if (title) { %> +

+ <%= title %> +

+<% } %> - <% if (greeting) { %> -

- <%= greeting %> -

- <% } %> +<% if (greeting) { %> +

+ <%= greeting %> +

+<% } %> - <% (message).forEach(function(paragraph) { %> -

- <%= paragraph %> -

- <% }) %> +<% (message).forEach(function(paragraph) { %> +

+ <%= paragraph %> +

+<% }) %> -

 

+ + + + +
+ + <%= ctaText %> + +
- - - - -
- - - - -
- - <%= ctaText %> - -
-
+<% if (secondaryMessage && secondaryMessage.length > 0) { %> + <% (secondaryMessage).forEach(function(paragraph) { %> +

+ <%= paragraph %> +

+ <% }) %> +<% } %> - <% if (secondaryMessage && secondaryMessage.length > 0) { %> -

 

+

+ If the button above does not appear, please copy and paste this link into your browser's address bar: +

+

+ <%= ctaURL %> +

- <% (secondaryMessage).forEach(function(paragraph) { %> -

- <%= paragraph %> -

- <% }) %> - <% } %> - -

 

- -

- If the button above does not appear, please copy and paste this link into your browser's address bar: -

- -

- <%= ctaURL %> -

-
-
- <% if (gmailGoToAction) { %> - - <% } %> -\ +<% if (gmailGoToAction) { %> + +<% } %>\ `) diff --git a/services/web/app/src/Features/Email/Bodies/updated-cta-email.mjs b/services/web/app/src/Features/Email/Bodies/updated-cta-email.mjs deleted file mode 100644 index 8987b62251..0000000000 --- a/services/web/app/src/Features/Email/Bodies/updated-cta-email.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import _ from 'lodash' -import { fontFamily, colors } from '../emailStyles.mjs' - -export default _.template(`\ -<% if (title) { %> -

- <%= title %> -

-<% } %> - -<% if (greeting) { %> -

- <%= greeting %> -

-<% } %> - -<% (message).forEach(function(paragraph) { %> -

- <%= paragraph %> -

-<% }) %> - - - - - -
- - <%= ctaText %> - -
- -<% if (secondaryMessage && secondaryMessage.length > 0) { %> - <% (secondaryMessage).forEach(function(paragraph) { %> -

- <%= paragraph %> -

- <% }) %> -<% } %> - -

- If the button above does not appear, please copy and paste this link into your browser's address bar: -

-

- <%= ctaURL %> -

- -<% if (gmailGoToAction) { %> - -<% } %>\ -`) diff --git a/services/web/app/src/Features/Email/EmailBuilder.mjs b/services/web/app/src/Features/Email/EmailBuilder.mjs index 69bb1f3adb..ecc6e341d3 100644 --- a/services/web/app/src/Features/Email/EmailBuilder.mjs +++ b/services/web/app/src/Features/Email/EmailBuilder.mjs @@ -4,12 +4,9 @@ import moment from 'moment' import EmailMessageHelper from './EmailMessageHelper.mjs' import StringHelper from '../Helpers/StringHelper.mjs' import BaseEmailLayout from './Layouts/BaseEmailLayout.mjs' -import UpdatedBaseEmailLayout from './Layouts/UpdatedBaseEmailLayout.mjs' import SpamSafe from './SpamSafe.mjs' import ctaEmailBody from './Bodies/cta-email.mjs' -import updatedCtaEmailBody from './Bodies/updated-cta-email.mjs' import NoCTAEmailBody from './Bodies/NoCTAEmailBody.mjs' -import UpdatedNoCTAEmailBody from './Bodies/UpdatedNoCTAEmailBody.mjs' function _emailBodyPlainText(content, opts, ctaEmail) { let emailBody = `${content.greeting(opts, true)}` @@ -79,10 +76,7 @@ function ctaTemplate(content) { return content.subject(opts) }, layout(opts) { - const layoutFn = opts.useNewEmailDesign - ? UpdatedBaseEmailLayout - : BaseEmailLayout - return layoutFn(opts) + return BaseEmailLayout(opts) }, footerMessage(opts) { return content.footerMessage(opts) @@ -91,8 +85,7 @@ function ctaTemplate(content) { return _emailBodyPlainText(content, opts, true) }, compiledTemplate(opts) { - const bodyFn = opts.useNewEmailDesign ? updatedCtaEmailBody : ctaEmailBody - return bodyFn({ + return ctaEmailBody({ title: content.title(opts), greeting: content.greeting(opts), message: content.message(opts), @@ -118,10 +111,7 @@ function NoCTAEmailTemplate(content) { return content.subject(opts) }, layout(opts) { - const layoutFn = opts.useNewEmailDesign - ? UpdatedBaseEmailLayout - : BaseEmailLayout - return layoutFn(opts) + return BaseEmailLayout(opts) }, plainTextTemplate(opts) { return `\ @@ -134,10 +124,7 @@ The ${settings.appName} Team - ${settings.siteUrl}\ ` }, compiledTemplate(opts) { - const bodyFn = opts.useNewEmailDesign - ? UpdatedNoCTAEmailBody - : NoCTAEmailBody - return bodyFn({ + return NoCTAEmailBody({ title: typeof content.title === 'function' ? content.title(opts) : undefined, greeting: content.greeting(opts), diff --git a/services/web/app/src/Features/Email/EmailHandler.mjs b/services/web/app/src/Features/Email/EmailHandler.mjs index 0c0b769688..054a578284 100644 --- a/services/web/app/src/Features/Email/EmailHandler.mjs +++ b/services/web/app/src/Features/Email/EmailHandler.mjs @@ -4,8 +4,6 @@ import logger from '@overleaf/logger' import EmailBuilder from './EmailBuilder.mjs' import EmailSender from './EmailSender.mjs' import Queues from '../../infrastructure/Queues.mjs' -import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs' -import UserGetter from '../User/UserGetter.mjs' const EMAIL_SETTINGS = Settings.email || {} @@ -14,27 +12,6 @@ const EMAIL_SETTINGS = Settings.email || {} * @param {object} opts */ async function sendEmail(emailType, opts) { - if (opts.to) { - try { - const user = await UserGetter.promises.getUserByAnyEmail(opts.to, { - _id: 1, - }) - if (user) { - const { variant } = - await SplitTestHandler.promises.getAssignmentForUser( - user._id.toString(), - 'new-email-design' - ) - opts.useNewEmailDesign = variant === 'enabled' - } - } catch (err) { - logger.warn( - { err }, - 'failed to get new-email-design split test assignment' - ) - } - } - const email = EmailBuilder.buildEmail(emailType, opts) if (email.type === 'lifecycle' && !EMAIL_SETTINGS.lifecycle) { return diff --git a/services/web/app/src/Features/Email/Layouts/BaseEmailLayout.mjs b/services/web/app/src/Features/Email/Layouts/BaseEmailLayout.mjs index 4242241135..174bd6d8d1 100644 --- a/services/web/app/src/Features/Email/Layouts/BaseEmailLayout.mjs +++ b/services/web/app/src/Features/Email/Layouts/BaseEmailLayout.mjs @@ -1,395 +1,169 @@ import _ from 'lodash' import settings from '@overleaf/settings' +import { fontFamily, colors } from '../emailStyles.mjs' + +const _isSaas = settings.env === 'saas' +const _imageBase = settings.cdn?.web?.host || settings.siteUrl || null + +let _logoHtml = `${settings.appName}` +let _taglineHtml = '' +if (_isSaas && _imageBase) { + _logoHtml = ` + + ${settings.appName} + + ` + + _taglineHtml = ` + + + The home of scientific and technical writing + + ` +} + +const _customFooter = settings.email?.template?.customFooter || '' export default _.template(`\ - + + + + + + + + - + + -@media only screen and (max-width: 596px) { - .small-float-center { - margin: 0 auto !important; - float: none !important; - text-align: center !important; - } + +
+                               +                               +
- .small-text-center { - text-align: center !important; - } + + + + + - table.body .container { - width: 95% !important; - } - - table.body .columns, - table.body .column { - height: auto !important; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; - padding-left: 16px !important; - padding-right: 16px !important; - } - - table.body .columns .column, - table.body .columns .columns, - table.body .column .column, - table.body .column .columns { - padding-left: 0 !important; - padding-right: 0 !important; - } - - table.body .collapse .columns, - table.body .collapse .column { - padding-left: 0 !important; - padding-right: 0 !important; - } - - td.small-1, - th.small-1 { - display: inline-block !important; - width: 8.33333% !important; - } - - td.small-2, - th.small-2 { - display: inline-block !important; - width: 16.66667% !important; - } - - td.small-3, - th.small-3 { - display: inline-block !important; - width: 25% !important; - } - - td.small-4, - th.small-4 { - display: inline-block !important; - width: 33.33333% !important; - } - - td.small-5, - th.small-5 { - display: inline-block !important; - width: 41.66667% !important; - } - - td.small-6, - th.small-6 { - display: inline-block !important; - width: 50% !important; - } - - td.small-7, - th.small-7 { - display: inline-block !important; - width: 58.33333% !important; - } - - td.small-8, - th.small-8 { - display: inline-block !important; - width: 66.66667% !important; - } - - td.small-9, - th.small-9 { - display: inline-block !important; - width: 75% !important; - } - - td.small-10, - th.small-10 { - display: inline-block !important; - width: 83.33333% !important; - } - - td.small-11, - th.small-11 { - display: inline-block !important; - width: 91.66667% !important; - } - - td.small-12, - th.small-12 { - display: inline-block !important; - width: 100% !important; - } - - .columns td.small-12, - .column td.small-12, - .columns th.small-12, - .column th.small-12 { - display: block !important; - width: 100% !important; - } - - table.body td.small-offset-1, - table.body th.small-offset-1 { - margin-left: 8.33333% !important; - Margin-left: 8.33333% !important; - } - - table.body td.small-offset-2, - table.body th.small-offset-2 { - margin-left: 16.66667% !important; - Margin-left: 16.66667% !important; - } - - table.body td.small-offset-3, - table.body th.small-offset-3 { - margin-left: 25% !important; - Margin-left: 25% !important; - } - - table.body td.small-offset-4, - table.body th.small-offset-4 { - margin-left: 33.33333% !important; - Margin-left: 33.33333% !important; - } - - table.body td.small-offset-5, - table.body th.small-offset-5 { - margin-left: 41.66667% !important; - Margin-left: 41.66667% !important; - } - - table.body td.small-offset-6, - table.body th.small-offset-6 { - margin-left: 50% !important; - Margin-left: 50% !important; - } - - table.body td.small-offset-7, - table.body th.small-offset-7 { - margin-left: 58.33333% !important; - Margin-left: 58.33333% !important; - } - - table.body td.small-offset-8, - table.body th.small-offset-8 { - margin-left: 66.66667% !important; - Margin-left: 66.66667% !important; - } - - table.body td.small-offset-9, - table.body th.small-offset-9 { - margin-left: 75% !important; - Margin-left: 75% !important; - } - - table.body td.small-offset-10, - table.body th.small-offset-10 { - margin-left: 83.33333% !important; - Margin-left: 83.33333% !important; - } - - table.body td.small-offset-11, - table.body th.small-offset-11 { - margin-left: 91.66667% !important; - Margin-left: 91.66667% !important; - } - - table.body table.columns td.expander, - table.body table.columns th.expander { - display: none !important; - } - - table.body .right-text-pad, - table.body .text-pad-right { - padding-left: 10px !important; - } - - table.body .left-text-pad, - table.body .text-pad-left { - padding-right: 10px !important; - } - - table.menu { - width: 100% !important; - } - - table.menu td, - table.menu th { - width: auto !important; - display: inline-block !important; - } - - table.menu.vertical td, - table.menu.vertical th, - table.menu.small-vertical td, - table.menu.small-vertical th { - display: block !important; - } - - table.menu[align="center"] { - width: auto !important; - } - - table.button.small-expand, - table.button.small-expanded { - width: 100% !important; - } - - table.button.small-expand table, - table.button.small-expanded table { - width: 100%; - } - - table.button.small-expand table a, - table.button.small-expanded table a { - text-align: center !important; - width: 100% !important; - padding-left: 0 !important; - padding-right: 0 !important; - } - - table.button.small-expand center, - table.button.small-expanded center { - min-width: 0; - } -} - - - - - - - -
-
- -
-
- - -
-
-

- ${settings.appName} -

-
-
-
-
 
-
-
 
- - <%= body %> - -
-
 
-

- ${ - settings.email && - settings.email.template && - settings.email.template.customFooter - ? `${settings.email.template.customFooter}
` - : '' - }${settings.appName} • ${ - settings.siteUrl - } -

- <% if (footerMessage) { %>

<%= footerMessage %>

<% } %> -
-
- -
-
- -
                                                           
- + \ `) diff --git a/services/web/app/src/Features/Email/Layouts/UpdatedBaseEmailLayout.mjs b/services/web/app/src/Features/Email/Layouts/UpdatedBaseEmailLayout.mjs deleted file mode 100644 index 174bd6d8d1..0000000000 --- a/services/web/app/src/Features/Email/Layouts/UpdatedBaseEmailLayout.mjs +++ /dev/null @@ -1,169 +0,0 @@ -import _ from 'lodash' -import settings from '@overleaf/settings' -import { fontFamily, colors } from '../emailStyles.mjs' - -const _isSaas = settings.env === 'saas' -const _imageBase = settings.cdn?.web?.host || settings.siteUrl || null - -let _logoHtml = `${settings.appName}` -let _taglineHtml = '' -if (_isSaas && _imageBase) { - _logoHtml = ` - - ${settings.appName} - - ` - - _taglineHtml = ` - - - The home of scientific and technical writing - - ` -} - -const _customFooter = settings.email?.template?.customFooter || '' - -export default _.template(`\ - - - - - - - - - - - - -
-                               -                               -
- - - - - - - - -\ -`) diff --git a/services/web/test/unit/src/Email/UpdatedBaseEmailLayout.test.mjs b/services/web/test/unit/src/Email/BaseEmailLayout.test.mjs similarity index 94% rename from services/web/test/unit/src/Email/UpdatedBaseEmailLayout.test.mjs rename to services/web/test/unit/src/Email/BaseEmailLayout.test.mjs index cf2df55888..e205f52326 100644 --- a/services/web/test/unit/src/Email/UpdatedBaseEmailLayout.test.mjs +++ b/services/web/test/unit/src/Email/BaseEmailLayout.test.mjs @@ -4,7 +4,7 @@ import path from 'node:path' const LAYOUT_PATH = path.join( import.meta.dirname, - '../../../../app/src/Features/Email/Layouts/UpdatedBaseEmailLayout' + '../../../../app/src/Features/Email/Layouts/BaseEmailLayout' ) async function buildLayoutWithEnv(env) { @@ -20,7 +20,7 @@ async function buildLayoutWithEnv(env) { return layout({ body: 'Test body', footerMessage: '' }) } -describe('UpdatedBaseEmailLayout branding', function () { +describe('BaseEmailLayout branding', function () { afterEach(function () { vi.restoreAllMocks() vi.resetModules() diff --git a/services/web/test/unit/src/Email/EmailBuilder.test.mjs b/services/web/test/unit/src/Email/EmailBuilder.test.mjs index 1ade94eda8..07e2939391 100644 --- a/services/web/test/unit/src/Email/EmailBuilder.test.mjs +++ b/services/web/test/unit/src/Email/EmailBuilder.test.mjs @@ -3,9 +3,6 @@ import cheerio from 'cheerio' import path from 'node:path' import EmailMessageHelper from '../../../../app/src/Features/Email/EmailMessageHelper.mjs' -import ctaEmailBody from '../../../../app/src/Features/Email/Bodies/cta-email.mjs' -import NoCTAEmailBody from '../../../../app/src/Features/Email/Bodies/NoCTAEmailBody.mjs' -import BaseEmailLayout from '../../../../app/src/Features/Email/Layouts/BaseEmailLayout.mjs' const MODULE_PATH = path.join( import.meta.dirname, @@ -23,24 +20,6 @@ describe('EmailBuilder', function () { default: EmailMessageHelper, })) - vi.doMock('../../../../app/src/Features/Email/Bodies/cta-email', () => ({ - default: ctaEmailBody, - })) - - vi.doMock( - '../../../../app/src/Features/Email/Bodies/NoCTAEmailBody', - () => ({ - default: NoCTAEmailBody, - }) - ) - - vi.doMock( - '../../../../app/src/Features/Email/Layouts/BaseEmailLayout', - () => ({ - default: BaseEmailLayout, - }) - ) - vi.doMock('@overleaf/settings', () => ({ default: ctx.settings, }))