From 0de4c98a58eb1a8cf1aff454647597144cd2d1dd Mon Sep 17 00:00:00 2001 From: Christopher Kolstad Date: Tue, 27 Apr 2021 09:05:46 +0200 Subject: [PATCH] fix: send email on process.nextTick (#805) To avoid users having to wait while we wait for a response from the email provider, we now send the mail on nextTick --- src/lib/routes/admin-api/user-admin.ts | 14 +++++- src/lib/services/email-service.test.ts | 14 +++--- src/lib/services/email-service.ts | 60 +++++++++++++++++++++++--- 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/lib/routes/admin-api/user-admin.ts b/src/lib/routes/admin-api/user-admin.ts index 5845ae91fb..bc613c1da3 100644 --- a/src/lib/routes/admin-api/user-admin.ts +++ b/src/lib/routes/admin-api/user-admin.ts @@ -7,6 +7,7 @@ import { handleErrors } from './util'; import { IUnleashConfig } from '../../types/option'; import { EmailService, MAIL_ACCEPTED } from '../../services/email-service'; import ResetTokenService from '../../services/reset-token-service'; +import { IUnleashServices } from '../../types/services'; const getCreatorUsernameOrPassword = req => req.user.username || req.user.email; @@ -23,7 +24,18 @@ export default class UserAdminController extends Controller { constructor( config: IUnleashConfig, - { userService, accessService, emailService, resetTokenService }, + { + userService, + accessService, + emailService, + resetTokenService, + }: Pick< + IUnleashServices, + | 'userService' + | 'accessService' + | 'emailService' + | 'resetTokenService' + >, ) { super(config); this.userService = userService; diff --git a/src/lib/services/email-service.test.ts b/src/lib/services/email-service.test.ts index b382150747..0080c11179 100644 --- a/src/lib/services/email-service.test.ts +++ b/src/lib/services/email-service.test.ts @@ -22,11 +22,10 @@ test('Can send reset email', async t => { 'test@resetLinkUrl.com', resetLinkUrl, ); - const message = JSON.parse(content.message); - t.is(message.from.address, 'noreply@getunleash.ai'); - t.is(message.subject, 'Unleash - Reset your password'); - t.true(message.html.includes(resetLinkUrl)); - t.true(message.text.includes(resetLinkUrl)); + t.is(content.from, 'noreply@getunleash.ai'); + t.is(content.subject, 'Unleash - Reset your password'); + t.true(content.html.includes(resetLinkUrl)); + t.true(content.text.includes(resetLinkUrl)); }); test('Can send welcome mail', async t => { @@ -46,7 +45,6 @@ test('Can send welcome mail', async t => { 'test@test.com', 'abc123456', ); - const message = JSON.parse(content.message); - t.is(message.from.address, 'noreply@getunleash.ai'); - t.is(message.subject, 'Welcome to Unleash'); + t.is(content.from, 'noreply@getunleash.ai'); + t.is(content.subject, 'Welcome to Unleash'); }); diff --git a/src/lib/services/email-service.ts b/src/lib/services/email-service.ts index c03e0d167b..ea1a29f80a 100644 --- a/src/lib/services/email-service.ts +++ b/src/lib/services/email-service.ts @@ -30,6 +30,14 @@ export interface IEmailOptions { transporterType: TransporterType; } +export interface IEmailEnvelope { + from: string; + to: string; + subject: string; + html: string; + text: string; +} + const RESET_MAIL_SUBJECT = 'Unleash - Reset your password'; const GETTING_STARTED_SUBJECT = 'Welcome to Unleash'; @@ -38,7 +46,7 @@ export const MAIL_ACCEPTED = '250 Accepted'; export class EmailService { private logger: Logger; - private mailer?: Transporter; + private readonly mailer?: Transporter; private readonly sender: string; @@ -67,7 +75,7 @@ export class EmailService { name: string, recipient: string, resetLink: string, - ): Promise { + ): Promise { if (this.configured()) { const year = new Date().getFullYear(); const bodyHtml = await this.compileTemplate( @@ -95,13 +103,32 @@ export class EmailService { html: bodyHtml, text: bodyText, }; - return this.mailer.sendMail(email); + process.nextTick(() => { + this.mailer.sendMail(email).then( + () => + this.logger.info( + 'Successfully sent reset-password email', + ), + e => + this.logger.warn( + 'Failed to send reset-password email', + e, + ), + ); + }); + return Promise.resolve(email); } return new Promise(res => { this.logger.warn( 'No mailer is configured. Please read the docs on how to configure an emailservice', ); - res({}); + res({ + from: this.sender, + to: recipient, + subject: RESET_MAIL_SUBJECT, + html: '', + text: '', + }); }); } @@ -109,7 +136,7 @@ export class EmailService { name: string, recipient: string, passwordLink: string, - ): Promise { + ): Promise { if (this.configured()) { const year = new Date().getFullYear(); const context = { passwordLink, name, year }; @@ -130,13 +157,32 @@ export class EmailService { html: bodyHtml, text: bodyText, }; - return this.mailer.sendMail(email); + process.nextTick(() => { + this.mailer.sendMail(email).then( + () => + this.logger.info( + 'Successfully sent getting started email', + ), + e => + this.logger.warn( + 'Failed to send getting started email', + e, + ), + ); + }); + return Promise.resolve(email); } return new Promise(res => { this.logger.warn( 'No mailer is configured. Please read the docs on how to configure an EmailService', ); - res({}); + res({ + from: this.sender, + to: recipient, + subject: GETTING_STARTED_SUBJECT, + html: '', + text: '', + }); }); }