From dbc10fe9f522f31eb06add6f3f6863ce24510547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Wed, 28 Aug 2024 17:15:18 +0200 Subject: [PATCH] feat(core): Switch to MJML for email templates (#10518) --- .vscode/extensions.json | 1 + .../src/configs/user-management.config.ts | 8 +- packages/@n8n/config/test/config.test.ts | 8 +- packages/cli/package.json | 1 + packages/cli/scripts/build.mjs | 24 +- .../controllers/password-reset.controller.ts | 6 +- packages/cli/src/services/user.service.ts | 2 - .../src/user-management/email/Interfaces.ts | 7 +- .../__tests__/user-management-mailer.test.ts | 22 +- .../src/user-management/email/node-mailer.ts | 11 +- .../email/templates/_common.mjml | 25 + .../email/templates/_logo.mjml | 5 + .../email/templates/credentials-shared.mjml | 18 + .../email/templates/credentialsShared.html | 4 - .../email/templates/instanceSetup.html | 5 - .../email/templates/invite.html | 4 - .../email/templates/n8n-logo.png | Bin 0 -> 1487 bytes .../templates/password-reset-requested.mjml | 31 + .../email/templates/passwordReset.html | 5 - .../email/templates/user-invited.mjml | 18 + .../email/templates/workflow-shared.mjml | 18 + .../email/templates/workflowShared.html | 4 - .../email/user-management-mailer.ts | 56 +- pnpm-lock.yaml | 557 +++++++++++++++++- 24 files changed, 754 insertions(+), 86 deletions(-) create mode 100644 packages/cli/src/user-management/email/templates/_common.mjml create mode 100644 packages/cli/src/user-management/email/templates/_logo.mjml create mode 100644 packages/cli/src/user-management/email/templates/credentials-shared.mjml delete mode 100644 packages/cli/src/user-management/email/templates/credentialsShared.html delete mode 100644 packages/cli/src/user-management/email/templates/instanceSetup.html delete mode 100644 packages/cli/src/user-management/email/templates/invite.html create mode 100644 packages/cli/src/user-management/email/templates/n8n-logo.png create mode 100644 packages/cli/src/user-management/email/templates/password-reset-requested.mjml delete mode 100644 packages/cli/src/user-management/email/templates/passwordReset.html create mode 100644 packages/cli/src/user-management/email/templates/user-invited.mjml create mode 100644 packages/cli/src/user-management/email/templates/workflow-shared.mjml delete mode 100644 packages/cli/src/user-management/email/templates/workflowShared.html diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8fb03eb716..158d03fdc8 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,6 +5,7 @@ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", "esbenp.prettier-vscode", + "mjmlio.vscode-mjml", "Vue.volar" ] } diff --git a/packages/@n8n/config/src/configs/user-management.config.ts b/packages/@n8n/config/src/configs/user-management.config.ts index 956bac2b75..06b3e64fea 100644 --- a/packages/@n8n/config/src/configs/user-management.config.ts +++ b/packages/@n8n/config/src/configs/user-management.config.ts @@ -49,19 +49,19 @@ class SmtpConfig { export class TemplateConfig { /** Overrides default HTML template for inviting new people (use full path) */ @Env('N8N_UM_EMAIL_TEMPLATES_INVITE') - invite: string = ''; + 'user-invited': string = ''; /** Overrides default HTML template for resetting password (use full path) */ @Env('N8N_UM_EMAIL_TEMPLATES_PWRESET') - passwordReset: string = ''; + 'password-reset-requested': string = ''; /** Overrides default HTML template for notifying that a workflow was shared (use full path) */ @Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_SHARED') - workflowShared: string = ''; + 'workflow-shared': string = ''; /** Overrides default HTML template for notifying that credentials were shared (use full path) */ @Env('N8N_UM_EMAIL_TEMPLATES_CREDENTIALS_SHARED') - credentialsShared: string = ''; + 'credentials-shared': string = ''; } @Config diff --git a/packages/@n8n/config/test/config.test.ts b/packages/@n8n/config/test/config.test.ts index 50e5c3b252..149cb1abbe 100644 --- a/packages/@n8n/config/test/config.test.ts +++ b/packages/@n8n/config/test/config.test.ts @@ -89,10 +89,10 @@ describe('GlobalConfig', () => { }, }, template: { - credentialsShared: '', - invite: '', - passwordReset: '', - workflowShared: '', + 'credentials-shared': '', + 'user-invited': '', + 'password-reset-requested': '', + 'workflow-shared': '', }, }, }, diff --git a/packages/cli/package.json b/packages/cli/package.json index fc43d35a8c..93be64e63c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -78,6 +78,7 @@ "chokidar": "^3.5.2", "concurrently": "^8.2.0", "ioredis-mock": "^8.8.1", + "mjml": "^4.15.3", "ts-essentials": "^7.0.3" }, "dependencies": { diff --git a/packages/cli/scripts/build.mjs b/packages/cli/scripts/build.mjs index f6d00d41da..ebe6886c41 100644 --- a/packages/cli/scripts/build.mjs +++ b/packages/cli/scripts/build.mjs @@ -3,6 +3,7 @@ import { writeFileSync } from 'fs'; import { fileURLToPath } from 'url'; import shell from 'shelljs'; import { rawTimeZones } from '@vvo/tzdb'; +import glob from 'fast-glob'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -13,7 +14,7 @@ const SPEC_THEME_FILENAME = 'swagger-theme.css'; const publicApiEnabled = process.env.N8N_PUBLIC_API_DISABLED !== 'true'; -copyUserManagementEmailTemplates(); +generateUserManagementEmailTemplates(); generateTimezoneData(); if (publicApiEnabled) { @@ -21,13 +22,22 @@ if (publicApiEnabled) { bundleOpenApiSpecs(); } -function copyUserManagementEmailTemplates() { - const templates = { - source: path.resolve(ROOT_DIR, 'src', 'user-management', 'email', 'templates'), - destination: path.resolve(ROOT_DIR, 'dist', 'user-management', 'email'), - }; +function generateUserManagementEmailTemplates() { + const sourceDir = path.resolve(ROOT_DIR, 'src', 'user-management', 'email', 'templates'); + const destinationDir = path.resolve(ROOT_DIR, 'dist', 'user-management', 'email', 'templates'); - shell.cp('-r', templates.source, templates.destination); + shell.mkdir('-p', destinationDir); + + const templates = glob.sync('*.mjml', { cwd: sourceDir }); + templates.forEach((template) => { + if (template.startsWith('_')) return; + const source = path.resolve(sourceDir, template); + const destination = path.resolve(destinationDir, template.replace(/\.mjml$/, '.handlebars')); + const command = `pnpm mjml --output ${destination} ${source}`; + shell.exec(command, { silent: false }); + }); + + shell.cp(path.resolve(sourceDir, 'n8n-logo.png'), destinationDir); } function copySwaggerTheme() { diff --git a/packages/cli/src/controllers/password-reset.controller.ts b/packages/cli/src/controllers/password-reset.controller.ts index 3f93ee08e9..22fb5d55a3 100644 --- a/packages/cli/src/controllers/password-reset.controller.ts +++ b/packages/cli/src/controllers/password-reset.controller.ts @@ -13,7 +13,6 @@ import { RESPONSE_ERROR_MESSAGES } from '@/constants'; import { MfaService } from '@/mfa/mfa.service'; import { Logger } from '@/logger'; import { ExternalHooks } from '@/external-hooks'; -import { UrlService } from '@/services/url.service'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; @@ -31,7 +30,6 @@ export class PasswordResetController { private readonly authService: AuthService, private readonly userService: UserService, private readonly mfaService: MfaService, - private readonly urlService: UrlService, private readonly license: License, private readonly passwordUtility: PasswordUtility, private readonly userRepository: UserRepository, @@ -108,14 +106,12 @@ export class PasswordResetController { const url = this.authService.generatePasswordResetUrl(user); - const { id, firstName, lastName } = user; + const { id, firstName } = user; try { await this.mailer.passwordReset({ email, firstName, - lastName, passwordResetUrl: url, - domain: this.urlService.getInstanceBaseUrl(), }); } catch (error) { this.eventService.emit('email-failed', { diff --git a/packages/cli/src/services/user.service.ts b/packages/cli/src/services/user.service.ts index 867cf7f603..ea18576f76 100644 --- a/packages/cli/src/services/user.service.ts +++ b/packages/cli/src/services/user.service.ts @@ -138,7 +138,6 @@ export class UserService { const result = await this.mailer.invite({ email, inviteAcceptUrl, - domain, }); if (result.emailSent) { invitedUser.user.emailSent = true; @@ -168,7 +167,6 @@ export class UserService { this.logger.error('Failed to send email', { userId: owner.id, inviteAcceptUrl, - domain, email, }); invitedUser.error = e.message; diff --git a/packages/cli/src/user-management/email/Interfaces.ts b/packages/cli/src/user-management/email/Interfaces.ts index 5116434998..65775079ad 100644 --- a/packages/cli/src/user-management/email/Interfaces.ts +++ b/packages/cli/src/user-management/email/Interfaces.ts @@ -1,17 +1,12 @@ export type InviteEmailData = { email: string; - firstName?: string; - lastName?: string; inviteAcceptUrl: string; - domain: string; }; export type PasswordResetData = { email: string; - firstName?: string; - lastName?: string; + firstName: string; passwordResetUrl: string; - domain: string; }; export type SendEmailResult = { diff --git a/packages/cli/src/user-management/email/__tests__/user-management-mailer.test.ts b/packages/cli/src/user-management/email/__tests__/user-management-mailer.test.ts index a52a240bc4..b08a8569e1 100644 --- a/packages/cli/src/user-management/email/__tests__/user-management-mailer.test.ts +++ b/packages/cli/src/user-management/email/__tests__/user-management-mailer.test.ts @@ -1,6 +1,7 @@ import type { GlobalConfig } from '@n8n/config'; import { mock } from 'jest-mock-extended'; +import type { UrlService } from '@/services/url.service'; import type { InviteEmailData, PasswordResetData } from '@/user-management/email/Interfaces'; import { NodeMailer } from '@/user-management/email/node-mailer'; import { UserManagementMailer } from '@/user-management/email/user-management-mailer'; @@ -31,7 +32,7 @@ describe('UserManagementMailer', () => { }, }, }); - const userManagementMailer = new UserManagementMailer(config, mock(), mock(), mock()); + const userManagementMailer = new UserManagementMailer(config, mock(), mock(), mock(), mock()); it('should not setup email transport', async () => { expect(userManagementMailer.isEmailSetUp).toBe(false); @@ -56,7 +57,18 @@ describe('UserManagementMailer', () => { }, }, }); - const userManagementMailer = new UserManagementMailer(config, mock(), mock(), mock()); + const urlService = mock(); + const userManagementMailer = new UserManagementMailer( + config, + mock(), + mock(), + urlService, + mock(), + ); + + beforeEach(() => { + urlService.getInstanceBaseUrl.mockReturnValue('https://n8n.url'); + }); it('should setup email transport', async () => { expect(userManagementMailer.isEmailSetUp).toBe(true); @@ -67,9 +79,7 @@ describe('UserManagementMailer', () => { const result = await userManagementMailer.invite(inviteEmailData); expect(result.emailSent).toBe(true); expect(nodeMailer.sendMail).toHaveBeenCalledWith({ - body: expect.stringContaining( - ``, - ), + body: expect.stringContaining(`href="${inviteEmailData.inviteAcceptUrl}"`), emailRecipients: email, subject: 'You have been invited to n8n', }); @@ -79,7 +89,7 @@ describe('UserManagementMailer', () => { const result = await userManagementMailer.passwordReset(passwordResetData); expect(result.emailSent).toBe(true); expect(nodeMailer.sendMail).toHaveBeenCalledWith({ - body: expect.stringContaining(``), + body: expect.stringContaining(`href="${passwordResetData.passwordResetUrl}"`), emailRecipients: email, subject: 'n8n password reset', }); diff --git a/packages/cli/src/user-management/email/node-mailer.ts b/packages/cli/src/user-management/email/node-mailer.ts index 2835dd7a31..8964ccc8e7 100644 --- a/packages/cli/src/user-management/email/node-mailer.ts +++ b/packages/cli/src/user-management/email/node-mailer.ts @@ -1,4 +1,5 @@ import { Service } from 'typedi'; +import path from 'node:path'; import { pick } from 'lodash'; import type { Transporter } from 'nodemailer'; import { createTransport } from 'nodemailer'; @@ -45,12 +46,20 @@ export class NodeMailer { async sendMail(mailData: MailData): Promise { try { - await this.transport?.sendMail({ + await this.transport.sendMail({ from: this.sender, to: mailData.emailRecipients, subject: mailData.subject, text: mailData.textOnly, html: mailData.body, + attachments: [ + { + cid: 'n8n-logo', + filename: 'n8n-logo.png', + path: path.resolve(__dirname, 'templates/n8n-logo.png'), + contentDisposition: 'inline', + }, + ], }); this.logger.debug( `Email sent successfully to the following recipients: ${mailData.emailRecipients.toString()}`, diff --git a/packages/cli/src/user-management/email/templates/_common.mjml b/packages/cli/src/user-management/email/templates/_common.mjml new file mode 100644 index 0000000000..38b13a16cd --- /dev/null +++ b/packages/cli/src/user-management/email/templates/_common.mjml @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/packages/cli/src/user-management/email/templates/_logo.mjml b/packages/cli/src/user-management/email/templates/_logo.mjml new file mode 100644 index 0000000000..a1f8031695 --- /dev/null +++ b/packages/cli/src/user-management/email/templates/_logo.mjml @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/cli/src/user-management/email/templates/credentials-shared.mjml b/packages/cli/src/user-management/email/templates/credentials-shared.mjml new file mode 100644 index 0000000000..10829961e4 --- /dev/null +++ b/packages/cli/src/user-management/email/templates/credentials-shared.mjml @@ -0,0 +1,18 @@ + + + + + + A credential has been shared with you + + + + + "{{ credentialsName }}" credential has been shared with you. + To access it, please click the button below. + Open credential + + + + + diff --git a/packages/cli/src/user-management/email/templates/credentialsShared.html b/packages/cli/src/user-management/email/templates/credentialsShared.html deleted file mode 100644 index b4634a9ddf..0000000000 --- a/packages/cli/src/user-management/email/templates/credentialsShared.html +++ /dev/null @@ -1,4 +0,0 @@ -

Hi there,

-

"{{ credentialsName }}" credential has been shared with you.

-

To view all the credentials you have access to within n8n, click the following link:

-

{{ credentialsListUrl }}

diff --git a/packages/cli/src/user-management/email/templates/instanceSetup.html b/packages/cli/src/user-management/email/templates/instanceSetup.html deleted file mode 100644 index 0a6c785912..0000000000 --- a/packages/cli/src/user-management/email/templates/instanceSetup.html +++ /dev/null @@ -1,5 +0,0 @@ -

Hi there!

-

Welcome to n8n, {{firstName}} {{lastName}}

-

Your instance is set up!

-

Use your email to login: {{email}} and the chosen password.

-

Have fun automating!

diff --git a/packages/cli/src/user-management/email/templates/invite.html b/packages/cli/src/user-management/email/templates/invite.html deleted file mode 100644 index 178017d19d..0000000000 --- a/packages/cli/src/user-management/email/templates/invite.html +++ /dev/null @@ -1,4 +0,0 @@ -

Hi there,

-

You have been invited to join n8n ({{ domain }}).

-

To accept, click the following link:

-

{{ inviteAcceptUrl }}

diff --git a/packages/cli/src/user-management/email/templates/n8n-logo.png b/packages/cli/src/user-management/email/templates/n8n-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e70349b4526d8a9f95f07b3f24fbe101e86905f2 GIT binary patch literal 1487 zcmZuxdo&XY7$2$Yu~|xsc`V#SC61Ai=kk6|6C=s{Igv+P#VzD9iD=%mC67#^vKuqc zX-FX+;e{4_xqji`+eW}{`xXej#eOXs5k%s0NGfZpAqm) zL7+r;3G$c4aEyS`QT8qt0%qAntk_)=fNdN3LvU;(1^A)=$$|>o_Pg`HUEA-o{|k=Y zzYBjDI6pam-ec}gLJLkg9&^SK4G{U91KfJ9Ce6!uRh1|w8l$cBezm}>i?Y1S@O`BK+Do6PGo zu~49tyi=Dx{OdQo2km9xlQ71_BNlBOR>K)7`Gp(u3X7xJX#$O{F^aAl@;n*VHRu&d z*gnmsG%K!Dzc`3z<=X=Z_CGb;pK9WR9ONjX6BO@CwgSk*Bj!g{w8rLS>N?)U8WK?bq{@5j63*Sfu7vmj}` zrwZVGX}SM?J92kcoO`P^JxS^$79SPwdAvPvOXQ}^+0p%B8S+iwBMHrlGEL}HQTo76 zHN|P`Hj9vij?|>`9=I&@;Za$%F{I9h26XP{`JrJZUl$Co2cUGX^wd#GUoG|doy82e1S+jF>z8`GSCP|DUDZe-MOmcv{Xl@>wR^5*_)xA zCGJpm(k+^zfl{OQAILn}Y9g8=oUvL|NsKzOA6Fm|A#N3_Qa>(-FUYr5F2ExndpT`a zTj5-US1^qEJ(XRXZ6KlvQhX-IBrHsJ4vT-LK}gEOO$8@!TuaEgba6TM9t5i z&kOp5U{^-_lB$Z!A6|zVHw^3AL42z3wuID!VCpd%%Tao`^nnz}XgYYqlbBmnC^Do)#}B)Ncv#xVZF=ELNSi==e}hNOI9`R zn)N#k)-9yB-6M&8B+4vQ_95me!xChwQ5jVtkoP#em!PR!Vb$KD^=me>U;Un2C=;8^ zFMOo`@n-l(h6;udI(>yq0>Xfun>BUm?DO%anNBCs4g9Es{J8B3@W$5{`p`0>=6Vl3 zw=4Y)pXrO8`gf^mH+?% literal 0 HcmV?d00001 diff --git a/packages/cli/src/user-management/email/templates/password-reset-requested.mjml b/packages/cli/src/user-management/email/templates/password-reset-requested.mjml new file mode 100644 index 0000000000..3e645fd153 --- /dev/null +++ b/packages/cli/src/user-management/email/templates/password-reset-requested.mjml @@ -0,0 +1,31 @@ + + + + + + Reset your n8n password + + + + + Hi {{firstName}}, + Somebody asked to reset your password on n8n at {{domain}} . + Click the following link to choose a new password. + Set a new password + + + The link is only valid for 20 minutes since this email was sent. + + + + + + + If you did not request this email, you can safely ignore this.
+ Your password will not be changed. +
+
+
+ +
+
diff --git a/packages/cli/src/user-management/email/templates/passwordReset.html b/packages/cli/src/user-management/email/templates/passwordReset.html deleted file mode 100644 index 865072275e..0000000000 --- a/packages/cli/src/user-management/email/templates/passwordReset.html +++ /dev/null @@ -1,5 +0,0 @@ -

Hi {{firstName}},

-

Somebody asked to reset your password on n8n ({{ domain }}).

-
-

Click the following link to choose a new password. The link is valid for 20 minutes.

-{{ passwordResetUrl }} diff --git a/packages/cli/src/user-management/email/templates/user-invited.mjml b/packages/cli/src/user-management/email/templates/user-invited.mjml new file mode 100644 index 0000000000..7c1e605e91 --- /dev/null +++ b/packages/cli/src/user-management/email/templates/user-invited.mjml @@ -0,0 +1,18 @@ + + + + + + Welcome to n8n! 🎉 + + + + + You have been invited to join n8n at {{domain}} . + To accept, please click the button below. + Set up your n8n account + + + + + diff --git a/packages/cli/src/user-management/email/templates/workflow-shared.mjml b/packages/cli/src/user-management/email/templates/workflow-shared.mjml new file mode 100644 index 0000000000..a59fbac7c5 --- /dev/null +++ b/packages/cli/src/user-management/email/templates/workflow-shared.mjml @@ -0,0 +1,18 @@ + + + + + + A workflow has been shared with you + + + + + "{{ workflowName }}" workflow has been shared with you. + To access it, please click the button below. + Open Workflow + + + + + diff --git a/packages/cli/src/user-management/email/templates/workflowShared.html b/packages/cli/src/user-management/email/templates/workflowShared.html deleted file mode 100644 index d6fa692759..0000000000 --- a/packages/cli/src/user-management/email/templates/workflowShared.html +++ /dev/null @@ -1,4 +0,0 @@ -

Hi there,

-

"{{ workflowName }}" workflow has been shared with you.

-

To access the workflow, click the following link:

-

{{ workflowUrl }}

diff --git a/packages/cli/src/user-management/email/user-management-mailer.ts b/packages/cli/src/user-management/email/user-management-mailer.ts index c91369df7b..f67d205a28 100644 --- a/packages/cli/src/user-management/email/user-management-mailer.ts +++ b/packages/cli/src/user-management/email/user-management-mailer.ts @@ -8,6 +8,7 @@ import { GlobalConfig } from '@n8n/config'; import type { User } from '@/databases/entities/User'; import type { WorkflowEntity } from '@/databases/entities/workflow-entity'; import { UserRepository } from '@/databases/repositories/user.repository'; +import { EventService } from '@/events/event.service'; import { Logger } from '@/logger'; import { UrlService } from '@/services/url.service'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; @@ -15,10 +16,14 @@ import { toError } from '@/utils'; import type { InviteEmailData, PasswordResetData, SendEmailResult } from './Interfaces'; import { NodeMailer } from './node-mailer'; -import { EventService } from '@/events/event.service'; +import { inTest } from '@/constants'; type Template = HandlebarsTemplateDelegate; -type TemplateName = 'invite' | 'passwordReset' | 'workflowShared' | 'credentialsShared'; +type TemplateName = + | 'user-invited' + | 'password-reset-requested' + | 'workflow-shared' + | 'credentials-shared'; @Service() export class UserManagementMailer { @@ -35,6 +40,7 @@ export class UserManagementMailer { private readonly logger: Logger, private readonly userRepository: UserRepository, private readonly urlService: UrlService, + private readonly eventService: EventService, ) { const emailsConfig = globalConfig.userManagement.emails; this.isEmailSetUp = emailsConfig.mode === 'smtp' && emailsConfig.smtp.host !== ''; @@ -49,31 +55,23 @@ export class UserManagementMailer { async invite(inviteEmailData: InviteEmailData): Promise { if (!this.mailer) return { emailSent: false }; - const template = await this.getTemplate('invite'); - const result = await this.mailer.sendMail({ + const template = await this.getTemplate('user-invited'); + return await this.mailer.sendMail({ emailRecipients: inviteEmailData.email, subject: 'You have been invited to n8n', - body: template(inviteEmailData), + body: template({ ...this.basePayload, ...inviteEmailData }), }); - - // If mailer does not exist it means mail has been disabled. - // No error, just say no email was sent. - return result ?? { emailSent: false }; } async passwordReset(passwordResetData: PasswordResetData): Promise { if (!this.mailer) return { emailSent: false }; - const template = await this.getTemplate('passwordReset', 'passwordReset.html'); - const result = await this.mailer.sendMail({ + const template = await this.getTemplate('password-reset-requested'); + return await this.mailer.sendMail({ emailRecipients: passwordResetData.email, subject: 'n8n password reset', - body: template(passwordResetData), + body: template({ ...this.basePayload, ...passwordResetData }), }); - - // If mailer does not exist it means mail has been disabled. - // No error, just say no email was sent. - return result ?? { emailSent: false }; } async notifyWorkflowShared({ @@ -93,7 +91,7 @@ export class UserManagementMailer { const emailRecipients = recipients.map(({ email }) => email); - const populateTemplate = await this.getTemplate('workflowShared', 'workflowShared.html'); + const populateTemplate = await this.getTemplate('workflow-shared'); const baseUrl = this.urlService.getInstanceBaseUrl(); @@ -111,7 +109,7 @@ export class UserManagementMailer { this.logger.info('Sent workflow shared email successfully', { sharerId: sharer.id }); - Container.get(EventService).emit('user-transactional-email-sent', { + this.eventService.emit('user-transactional-email-sent', { userId: sharer.id, messageType: 'Workflow shared', publicApi: false, @@ -119,7 +117,7 @@ export class UserManagementMailer { return result; } catch (e) { - Container.get(EventService).emit('email-failed', { + this.eventService.emit('email-failed', { user: sharer, messageType: 'Workflow shared', publicApi: false, @@ -148,7 +146,7 @@ export class UserManagementMailer { const emailRecipients = recipients.map(({ email }) => email); - const populateTemplate = await this.getTemplate('credentialsShared', 'credentialsShared.html'); + const populateTemplate = await this.getTemplate('credentials-shared'); const baseUrl = this.urlService.getInstanceBaseUrl(); @@ -166,7 +164,7 @@ export class UserManagementMailer { this.logger.info('Sent credentials shared email successfully', { sharerId: sharer.id }); - Container.get(EventService).emit('user-transactional-email-sent', { + this.eventService.emit('user-transactional-email-sent', { userId: sharer.id, messageType: 'Credentials shared', publicApi: false, @@ -174,7 +172,7 @@ export class UserManagementMailer { return result; } catch (e) { - Container.get(EventService).emit('email-failed', { + this.eventService.emit('email-failed', { user: sharer, messageType: 'Credentials shared', publicApi: false, @@ -186,21 +184,25 @@ export class UserManagementMailer { } } - async getTemplate( - templateName: TemplateName, - defaultFilename = `${templateName}.html`, - ): Promise