From 199036d467b78d2c5513bbc2804edb48379ca59e Mon Sep 17 00:00:00 2001 From: Thomas Taylor Date: Fri, 10 May 2024 18:37:31 +0100 Subject: [PATCH 01/27] Add Azure support --- conf/config.schema.json | 5 +++++ lib/MailerModule.js | 47 ++++++++++++++++++++++++++++++++++++----- lib/typedefs.js | 9 -------- package.json | 2 +- 4 files changed, 48 insertions(+), 15 deletions(-) delete mode 100644 lib/typedefs.js diff --git a/conf/config.schema.json b/conf/config.schema.json index 4cf5ef8..12786dc 100644 --- a/conf/config.schema.json +++ b/conf/config.schema.json @@ -18,6 +18,11 @@ "defaultSenderAddress": { "description": "Default email address to use as the sender", "type": "string" + }, + "pollerWaitTime": { + "description": "How long the send success poller waits between polls (in seconds)", + "type": "number", + "default": 1 } }, "if": { diff --git a/lib/MailerModule.js b/lib/MailerModule.js index a511ddb..79c3f93 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -1,5 +1,5 @@ import { AbstractModule } from 'adapt-authoring-core' -import nodemailer from 'nodemailer' +import { EmailClient, KnownEmailSendStatus } from '@azure/communication-email' /** * Mailer Module * @memberof mailer @@ -19,13 +19,12 @@ class MailerModule extends AbstractModule { */ this.connectionUrl = this.getConfig('connectionUrl') /** - * The Nodemailer SMTP transport instance - * @type {Nodemailer~Transport} + * The SMTP transport instance */ this.transporter = undefined if (this.isEnabled) { - this.transporter = nodemailer.createTransport(this.connectionUrl) + await this.initTransporter() await this.testConnection() } // note we still enable the API route if mailer is disabled to allow for testing @@ -45,6 +44,44 @@ class MailerModule extends AbstractModule { auth.unsecureRoute(`${router.path}/test`, 'post') } + async transporterInit () { + this.transporter = new EmailClient(this.connectionUrl) + } + + async transporterSend (data) { + const POLLER_WAIT_TIME = this.getConfig('pollerWaitTime') + const message = { + senderAddress: data.from, + content: { + subject: data.subject, + plainText: data.text, + html: data.html + }, + recipients: { + to: [{ address: data.to }] + } + } + const poller = await this.transporter.beginSend(message) + + if (!poller.getOperationState().isStarted) { + throw new Error('Poller was not started.') + } + let timeElapsed = 0 + while (!poller.isDone()) { + poller.poll() + + await new Promise(resolve => setTimeout(resolve, POLLER_WAIT_TIME * 1000)) + timeElapsed += 10 + + if (timeElapsed > 18 * POLLER_WAIT_TIME) { + throw new Error('Polling timed out.') + } + } + if (poller.getResult().status !== KnownEmailSendStatus.Succeeded) { + throw poller.getResult().error + } + } + /** * Checks the provided SMTP settings using nodemailer.verify. * @return {Promise} @@ -79,7 +116,7 @@ class MailerModule extends AbstractModule { const jsonschema = await this.app.waitForModule('jsonschema') const schema = await jsonschema.getSchema('maildata') await schema.validate(data) - await this.transporter.sendMail(data) + await this.transporterSend(data) this.log('info', 'email sent successfully') } catch (e) { throw this.app.errors.MAIL_SEND_FAILED.setData({ email: data.to, error: e }) diff --git a/lib/typedefs.js b/lib/typedefs.js deleted file mode 100644 index cf7002c..0000000 --- a/lib/typedefs.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * This file exists to define the below types for documentation purposes. - */ -/** - * Nodemailer SMTP transport - * @memberof mailer - * @external nodemailer~Transport - * @see {@link https://nodemailer.com/smtp/} - */ diff --git a/package.json b/package.json index e49d07f..6e77589 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "main": "index.js", "repository": "github:adapt-security/adapt-authoring-mailer", "dependencies": { - "nodemailer": "^6.9.9" + "@azure/communication-email": "^1.0.0" }, "peerDependencies": { "adapt-authoring-core": "github:adapt-security/adapt-authoring-core" From fca3b211017719a600a1b1b56520652485c60933 Mon Sep 17 00:00:00 2001 From: Thomas Taylor Date: Fri, 10 May 2024 18:43:11 +0100 Subject: [PATCH 02/27] Remove verification for now --- lib/MailerModule.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/MailerModule.js b/lib/MailerModule.js index 79c3f93..1d7a73f 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -87,12 +87,7 @@ class MailerModule extends AbstractModule { * @return {Promise} */ async testConnection () { - try { - await this.transporter.verify() - this.log('info', 'SMTP connection verified successfully') - } catch (e) { - this.log('warn', `SMTP connection test failed, ${e}`) - } + this.log('info', 'SMTP connection verification skipped') } /** From 9d8e182aa49fef5354eff47072441e7e117c9723 Mon Sep 17 00:00:00 2001 From: Thomas Taylor Date: Fri, 10 May 2024 20:05:06 +0100 Subject: [PATCH 03/27] Fix typo --- lib/MailerModule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/MailerModule.js b/lib/MailerModule.js index 1d7a73f..7ea2a48 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -24,7 +24,7 @@ class MailerModule extends AbstractModule { this.transporter = undefined if (this.isEnabled) { - await this.initTransporter() + await this.transporterInit() await this.testConnection() } // note we still enable the API route if mailer is disabled to allow for testing From 24d698a5a3cb9f784f16fa5bcd6e6759134cc76f Mon Sep 17 00:00:00 2001 From: Thomas Taylor Date: Tue, 14 May 2024 18:25:56 +0100 Subject: [PATCH 04/27] Refactor --- conf/config.schema.json | 10 ++--- lib/AbstractMailTransport.js | 23 ++++++++++ lib/MailerModule.js | 77 +++++++++++++------------------- lib/SmtpTransport.js | 29 ++++++++++++ lib/transports/AzureTransport.js | 55 +++++++++++++++++++++++ 5 files changed, 142 insertions(+), 52 deletions(-) create mode 100644 lib/AbstractMailTransport.js create mode 100644 lib/SmtpTransport.js create mode 100644 lib/transports/AzureTransport.js diff --git a/conf/config.schema.json b/conf/config.schema.json index 12786dc..78a28fb 100644 --- a/conf/config.schema.json +++ b/conf/config.schema.json @@ -10,6 +10,11 @@ "isPublic": true } }, + "defaultMailTransport": { + "description": "Mail transport to use by default", + "type": "string", + "format": "smtp" + }, "connectionUrl": { "description": "Connection URL for the SMTP service", "type": "string", @@ -18,11 +23,6 @@ "defaultSenderAddress": { "description": "Default email address to use as the sender", "type": "string" - }, - "pollerWaitTime": { - "description": "How long the send success poller waits between polls (in seconds)", - "type": "number", - "default": 1 } }, "if": { diff --git a/lib/AbstractMailTransport.js b/lib/AbstractMailTransport.js new file mode 100644 index 0000000..6b24ac7 --- /dev/null +++ b/lib/AbstractMailTransport.js @@ -0,0 +1,23 @@ +/** + * An abstract class which encompasses functions related to a single mail transport type + * @memberof mailer + */ +class AbstractMailTransport { + /** + * Performs any necessary initialisation steps for this transport + */ + async init () {} + + /** + * Sends an email + * @param {MailData} data + */ + async send (data) {} + + /** + * Performs any useful tests to check transport is working correctly + */ + async test () {} +} + +export default AbstractMailTransport diff --git a/lib/MailerModule.js b/lib/MailerModule.js index 7ea2a48..8ee7bcf 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -1,5 +1,6 @@ import { AbstractModule } from 'adapt-authoring-core' -import { EmailClient, KnownEmailSendStatus } from '@azure/communication-email' +import AbstractMailTransport from './AbstractMailTransport' +import SmtpTransport from './transports/SmtpTransport' /** * Mailer Module * @memberof mailer @@ -19,14 +20,10 @@ class MailerModule extends AbstractModule { */ this.connectionUrl = this.getConfig('connectionUrl') /** - * The SMTP transport instance + * Registered mail transports + * @type {Object} */ - this.transporter = undefined - - if (this.isEnabled) { - await this.transporterInit() - await this.testConnection() - } + this.transports = {} // note we still enable the API route if mailer is disabled to allow for testing const [auth, server] = await this.app.waitForModule('auth', 'server') const router = server.api.createChildRouter('mailer') @@ -42,52 +39,35 @@ class MailerModule extends AbstractModule { } }) auth.unsecureRoute(`${router.path}/test`, 'post') - } - - async transporterInit () { - this.transporter = new EmailClient(this.connectionUrl) - } - async transporterSend (data) { - const POLLER_WAIT_TIME = this.getConfig('pollerWaitTime') - const message = { - senderAddress: data.from, - content: { - subject: data.subject, - plainText: data.text, - html: data.html - }, - recipients: { - to: [{ address: data.to }] - } + if (this.isEnabled) { + // add the standard transport + this.registerTransport(SmtpTransport) + this.app.onReady.then(() => this.initTransports()) } - const poller = await this.transporter.beginSend(message) + } - if (!poller.getOperationState().isStarted) { - throw new Error('Poller was not started.') + registerTransport (TransportClass) { + let t + try { + t = new TransportClass() + this.transports[t.name] = t + } catch (e) { + this.log('error', `Failed to create transport, ${e}`) } - let timeElapsed = 0 - while (!poller.isDone()) { - poller.poll() - - await new Promise(resolve => setTimeout(resolve, POLLER_WAIT_TIME * 1000)) - timeElapsed += 10 - - if (timeElapsed > 18 * POLLER_WAIT_TIME) { - throw new Error('Polling timed out.') - } + if (!(t instanceof AbstractMailTransport)) { + this.log('error', 'Failed to create transport, not an instance of AbstractMailTransport') } - if (poller.getResult().status !== KnownEmailSendStatus.Succeeded) { - throw poller.getResult().error + if (!t.name) { + this.log('error', 'Failed to create transport, does not define a name') } } - /** - * Checks the provided SMTP settings using nodemailer.verify. - * @return {Promise} - */ - async testConnection () { - this.log('info', 'SMTP connection verification skipped') + async initTransports () { + await Promise.all(Object.values(this.transports).map(async t => { + await t.init() + await t.test() + })) } /** @@ -111,7 +91,10 @@ class MailerModule extends AbstractModule { const jsonschema = await this.app.waitForModule('jsonschema') const schema = await jsonschema.getSchema('maildata') await schema.validate(data) - await this.transporterSend(data) + + const transport = this.transports[options.transport ?? this.getConfig('defaultMailTransport')] + await transport.send(data) + this.log('info', 'email sent successfully') } catch (e) { throw this.app.errors.MAIL_SEND_FAILED.setData({ email: data.to, error: e }) diff --git a/lib/SmtpTransport.js b/lib/SmtpTransport.js new file mode 100644 index 0000000..75c1fc7 --- /dev/null +++ b/lib/SmtpTransport.js @@ -0,0 +1,29 @@ +import nodemailer from 'nodemailer' +/** + * SMTP mail transport + * @memberof mailer + * @extends {AbstractMailTransport} + */ +class SmtpTransport { + /** @override */ + async init () { + this.transporter = nodemailer.createTransport(this.connectionUrl) + } + + /** @override */ + async send (data) { + return this.transporter.sendMail(data) + } + + /** @override */ + async test () { + try { + await this.transporter.verify() + this.log('info', 'SMTP connection verified successfully') + } catch (e) { + this.log('warn', `SMTP connection test failed, ${e}`) + } + } +} + +export default SmtpTransport diff --git a/lib/transports/AzureTransport.js b/lib/transports/AzureTransport.js new file mode 100644 index 0000000..525b18f --- /dev/null +++ b/lib/transports/AzureTransport.js @@ -0,0 +1,55 @@ +import { AbstractMailTransport } from './AbstractMailTransport' +import { EmailClient, KnownEmailSendStatus } from '@azure/communication-email' +/** + * Microsoft Azure Mail Transport + * @memberof mailer + * @extends {AbstractMailTransport} + */ +class AzureTransport extends AbstractMailTransport { + /** @override */ + async init () { + this.name = 'azure' + this.client = new EmailClient(this.connectionUrl) + } + + /** @override */ + async send (data) { + const message = { + senderAddress: data.from, + content: { + subject: data.subject, + plainText: data.text, + html: data.html + }, + recipients: { + to: [{ address: data.to }] + } + } + const poller = await this.client.beginSend(message) + + if (!poller.getOperationState().isStarted) { + throw new Error('Poller was not started.') + } + let timeElapsed = 0 + while (!poller.isDone()) { + poller.poll() + + await new Promise(resolve => setTimeout(resolve, 1000)) + timeElapsed += 10 + + if (timeElapsed > 18) { + throw new Error('Polling timed out.') + } + } + if (poller.getResult().status !== KnownEmailSendStatus.Succeeded) { + throw poller.getResult().error + } + } + + /** @override */ + async test () { + this.log('info', 'SMTP connection verification skipped') + } +} + +export default AzureTransport From 66a64677e2ecce01df5c3a0ff2e48a42a7a0dea9 Mon Sep 17 00:00:00 2001 From: Thomas Taylor Date: Tue, 14 May 2024 18:27:13 +0100 Subject: [PATCH 05/27] Add azure mailer --- lib/MailerModule.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/MailerModule.js b/lib/MailerModule.js index 8ee7bcf..1bcb0ad 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -1,5 +1,6 @@ import { AbstractModule } from 'adapt-authoring-core' import AbstractMailTransport from './AbstractMailTransport' +import AzureTransport from './transports/AzureTransport' import SmtpTransport from './transports/SmtpTransport' /** * Mailer Module @@ -42,6 +43,7 @@ class MailerModule extends AbstractModule { if (this.isEnabled) { // add the standard transport + this.registerTransport(AzureTransport) this.registerTransport(SmtpTransport) this.app.onReady.then(() => this.initTransports()) } From 6ad3ec64c0ef833f19430a3cc8ba4f50f86798c8 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 11:42:54 +0100 Subject: [PATCH 06/27] Fix imports --- lib/MailerModule.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/MailerModule.js b/lib/MailerModule.js index 1bcb0ad..dd91fad 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -1,7 +1,7 @@ import { AbstractModule } from 'adapt-authoring-core' -import AbstractMailTransport from './AbstractMailTransport' -import AzureTransport from './transports/AzureTransport' -import SmtpTransport from './transports/SmtpTransport' +import AbstractMailTransport from './AbstractMailTransport.js' +import AzureTransport from './transports/AzureTransport.js' +import SmtpTransport from './transports/SmtpTransport.js' /** * Mailer Module * @memberof mailer From 455e7b775bccbca293345cf1b81628ded022afc9 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 12:02:27 +0100 Subject: [PATCH 07/27] Move file --- lib/{ => transports}/SmtpTransport.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lib/{ => transports}/SmtpTransport.js (100%) diff --git a/lib/SmtpTransport.js b/lib/transports/SmtpTransport.js similarity index 100% rename from lib/SmtpTransport.js rename to lib/transports/SmtpTransport.js From 5e68342266531f452c8d504e5ff6b387f5981606 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 12:19:11 +0100 Subject: [PATCH 08/27] Add inheritance --- lib/transports/SmtpTransport.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/transports/SmtpTransport.js b/lib/transports/SmtpTransport.js index 75c1fc7..33c0af2 100644 --- a/lib/transports/SmtpTransport.js +++ b/lib/transports/SmtpTransport.js @@ -1,10 +1,11 @@ import nodemailer from 'nodemailer' +import AbstractMailTransport from '../AbstractMailTransport.js' /** * SMTP mail transport * @memberof mailer * @extends {AbstractMailTransport} */ -class SmtpTransport { +class SmtpTransport extends AbstractMailTransport { /** @override */ async init () { this.transporter = nodemailer.createTransport(this.connectionUrl) From 6303d828fb2bf63cc775a040f607264239b684b2 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 12:19:22 +0100 Subject: [PATCH 09/27] Fix import path --- lib/transports/AzureTransport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/transports/AzureTransport.js b/lib/transports/AzureTransport.js index 525b18f..b4bc007 100644 --- a/lib/transports/AzureTransport.js +++ b/lib/transports/AzureTransport.js @@ -1,4 +1,4 @@ -import { AbstractMailTransport } from './AbstractMailTransport' +import { AbstractMailTransport } from '../AbstractMailTransport' import { EmailClient, KnownEmailSendStatus } from '@azure/communication-email' /** * Microsoft Azure Mail Transport From 347047fe71489ce879bbd153107229bdaaeef104 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 12:19:31 +0100 Subject: [PATCH 10/27] Add export --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 91cd1c8..46e694e 100644 --- a/index.js +++ b/index.js @@ -2,4 +2,5 @@ * Email sending utilities * @namespace mailer */ +export { default as AbstractMailTransport } from './lib/AbstractMailTransport.js' export { default } from './lib/MailerModule.js' From 60ac466a4c13077d5c23eaa0c7421e14cad037e2 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 12:32:37 +0100 Subject: [PATCH 11/27] Fix import path --- lib/transports/AzureTransport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/transports/AzureTransport.js b/lib/transports/AzureTransport.js index b4bc007..1b2423e 100644 --- a/lib/transports/AzureTransport.js +++ b/lib/transports/AzureTransport.js @@ -1,4 +1,4 @@ -import { AbstractMailTransport } from '../AbstractMailTransport' +import { AbstractMailTransport } from '../AbstractMailTransport.js' import { EmailClient, KnownEmailSendStatus } from '@azure/communication-email' /** * Microsoft Azure Mail Transport From 22535d2a4eb8736b3074df25701601fa6a7dd89c Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 12:46:56 +0100 Subject: [PATCH 12/27] Add missing dependency --- lib/transports/SmtpTransport.js | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/transports/SmtpTransport.js b/lib/transports/SmtpTransport.js index 33c0af2..fdc933f 100644 --- a/lib/transports/SmtpTransport.js +++ b/lib/transports/SmtpTransport.js @@ -1,5 +1,5 @@ -import nodemailer from 'nodemailer' import AbstractMailTransport from '../AbstractMailTransport.js' +import nodemailer from 'nodemailer' /** * SMTP mail transport * @memberof mailer diff --git a/package.json b/package.json index 6e77589..d145afa 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "main": "index.js", "repository": "github:adapt-security/adapt-authoring-mailer", "dependencies": { - "@azure/communication-email": "^1.0.0" + "@azure/communication-email": "^1.0.0", + "nodemailer": "^6.9.9" }, "peerDependencies": { "adapt-authoring-core": "github:adapt-security/adapt-authoring-core" From 9e54647db2d4c18aeb2f1b279faf57a1105a6350 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 15:48:43 +0100 Subject: [PATCH 13/27] Fix import --- lib/transports/AzureTransport.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/transports/AzureTransport.js b/lib/transports/AzureTransport.js index 1b2423e..5811884 100644 --- a/lib/transports/AzureTransport.js +++ b/lib/transports/AzureTransport.js @@ -1,4 +1,4 @@ -import { AbstractMailTransport } from '../AbstractMailTransport.js' +import AbstractMailTransport from '../AbstractMailTransport.js' import { EmailClient, KnownEmailSendStatus } from '@azure/communication-email' /** * Microsoft Azure Mail Transport From 20a76b6722f9714c99ad83187b02c25559bfcbc3 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 16:25:24 +0100 Subject: [PATCH 14/27] Fix typo --- lib/MailerModule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/MailerModule.js b/lib/MailerModule.js index dd91fad..248a420 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -45,7 +45,7 @@ class MailerModule extends AbstractModule { // add the standard transport this.registerTransport(AzureTransport) this.registerTransport(SmtpTransport) - this.app.onReady.then(() => this.initTransports()) + this.app.onReady().then(() => this.initTransports()) } } From 961e9e90ba89225a922a5826d2088efeb3db3413 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 17:27:48 +0100 Subject: [PATCH 15/27] Add transport name --- lib/transports/SmtpTransport.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/transports/SmtpTransport.js b/lib/transports/SmtpTransport.js index fdc933f..1f1d1c3 100644 --- a/lib/transports/SmtpTransport.js +++ b/lib/transports/SmtpTransport.js @@ -8,6 +8,7 @@ import nodemailer from 'nodemailer' class SmtpTransport extends AbstractMailTransport { /** @override */ async init () { + this.name = 'smtp' this.transporter = nodemailer.createTransport(this.connectionUrl) } From b59c4af362ba8e53802df906047643489af1c8f4 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 17:38:20 +0100 Subject: [PATCH 16/27] Refactor to only allow one mail transport --- conf/config.schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/config.schema.json b/conf/config.schema.json index 78a28fb..a512e81 100644 --- a/conf/config.schema.json +++ b/conf/config.schema.json @@ -10,8 +10,8 @@ "isPublic": true } }, - "defaultMailTransport": { - "description": "Mail transport to use by default", + "mailTransport": { + "description": "Mail transport to use", "type": "string", "format": "smtp" }, From 68603c3d95289142e3edb3a81e7dba315150a557 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 15 May 2024 17:38:44 +0100 Subject: [PATCH 17/27] Refactor --- lib/MailerModule.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/MailerModule.js b/lib/MailerModule.js index 248a420..77c54da 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -65,11 +65,14 @@ class MailerModule extends AbstractModule { } } + getTransport() { + return this.transports[this.getConfig('mailTransport')] + } + async initTransports () { - await Promise.all(Object.values(this.transports).map(async t => { - await t.init() - await t.test() - })) + const transport = this.getTransport() + await transport.init() + await transport.test() } /** @@ -94,8 +97,7 @@ class MailerModule extends AbstractModule { const schema = await jsonschema.getSchema('maildata') await schema.validate(data) - const transport = this.transports[options.transport ?? this.getConfig('defaultMailTransport')] - await transport.send(data) + await this.getTransport().send(data) this.log('info', 'email sent successfully') } catch (e) { From cde22aba7bd66406a7a621f2c9e190106be68ed5 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Thu, 16 May 2024 11:11:21 +0100 Subject: [PATCH 18/27] Refactor --- lib/AbstractMailTransport.js | 7 ++----- lib/MailerModule.js | 1 - lib/transports/AzureTransport.js | 11 ++++------- lib/transports/SmtpTransport.js | 10 +++------- 4 files changed, 9 insertions(+), 20 deletions(-) diff --git a/lib/AbstractMailTransport.js b/lib/AbstractMailTransport.js index 6b24ac7..f12c41d 100644 --- a/lib/AbstractMailTransport.js +++ b/lib/AbstractMailTransport.js @@ -3,11 +3,8 @@ * @memberof mailer */ class AbstractMailTransport { - /** - * Performs any necessary initialisation steps for this transport - */ - async init () {} - + name; + /** * Sends an email * @param {MailData} data diff --git a/lib/MailerModule.js b/lib/MailerModule.js index 77c54da..b1ae2ec 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -71,7 +71,6 @@ class MailerModule extends AbstractModule { async initTransports () { const transport = this.getTransport() - await transport.init() await transport.test() } diff --git a/lib/transports/AzureTransport.js b/lib/transports/AzureTransport.js index 5811884..4fa4db2 100644 --- a/lib/transports/AzureTransport.js +++ b/lib/transports/AzureTransport.js @@ -6,12 +6,8 @@ import { EmailClient, KnownEmailSendStatus } from '@azure/communication-email' * @extends {AbstractMailTransport} */ class AzureTransport extends AbstractMailTransport { - /** @override */ - async init () { - this.name = 'azure' - this.client = new EmailClient(this.connectionUrl) - } - + name = 'azure' + /** @override */ async send (data) { const message = { @@ -25,7 +21,8 @@ class AzureTransport extends AbstractMailTransport { to: [{ address: data.to }] } } - const poller = await this.client.beginSend(message) + const client = new EmailClient(this.connectionUrl) + const poller = await client.beginSend(message) if (!poller.getOperationState().isStarted) { throw new Error('Poller was not started.') diff --git a/lib/transports/SmtpTransport.js b/lib/transports/SmtpTransport.js index 1f1d1c3..755baf9 100644 --- a/lib/transports/SmtpTransport.js +++ b/lib/transports/SmtpTransport.js @@ -6,21 +6,17 @@ import nodemailer from 'nodemailer' * @extends {AbstractMailTransport} */ class SmtpTransport extends AbstractMailTransport { - /** @override */ - async init () { - this.name = 'smtp' - this.transporter = nodemailer.createTransport(this.connectionUrl) - } + name = 'smtp' /** @override */ async send (data) { - return this.transporter.sendMail(data) + return nodemailer.createTransport(this.connectionUrl).sendMail(data) } /** @override */ async test () { try { - await this.transporter.verify() + await nodemailer.createTransport(this.connectionUrl).verify() this.log('info', 'SMTP connection verified successfully') } catch (e) { this.log('warn', `SMTP connection test failed, ${e}`) From d613405bb3fc4e67cbea4b14fd6d268f95ed1d4f Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Thu, 16 May 2024 11:14:27 +0100 Subject: [PATCH 19/27] Rename config property --- conf/config.schema.json | 2 +- lib/MailerModule.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/conf/config.schema.json b/conf/config.schema.json index a512e81..c33a81a 100644 --- a/conf/config.schema.json +++ b/conf/config.schema.json @@ -10,7 +10,7 @@ "isPublic": true } }, - "mailTransport": { + "transport": { "description": "Mail transport to use", "type": "string", "format": "smtp" diff --git a/lib/MailerModule.js b/lib/MailerModule.js index b1ae2ec..a6ab336 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -66,7 +66,11 @@ class MailerModule extends AbstractModule { } getTransport() { - return this.transports[this.getConfig('mailTransport')] + const transportName = this.getConfig('transport') + if(!this.transports[transportName]) { + throw new Error(`No transport with name ${transportName}`) + } + return this.transports[transportName] } async initTransports () { From 13c79e280c9ac7634a3b1a857ae3759a5b6c1af9 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Thu, 16 May 2024 11:28:34 +0100 Subject: [PATCH 20/27] Update test error handling --- lib/MailerModule.js | 7 ++++++- lib/transports/AzureTransport.js | 5 ----- lib/transports/SmtpTransport.js | 7 +------ 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/MailerModule.js b/lib/MailerModule.js index a6ab336..22e91c8 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -75,7 +75,12 @@ class MailerModule extends AbstractModule { async initTransports () { const transport = this.getTransport() - await transport.test() + try { + await transport.test() + this.log('info', `${transport.name} connection verified successfully`) + } catch (e) { + this.log('warn', `${transport.name} connection test failed, ${e}`) + } } /** diff --git a/lib/transports/AzureTransport.js b/lib/transports/AzureTransport.js index 4fa4db2..208589d 100644 --- a/lib/transports/AzureTransport.js +++ b/lib/transports/AzureTransport.js @@ -42,11 +42,6 @@ class AzureTransport extends AbstractMailTransport { throw poller.getResult().error } } - - /** @override */ - async test () { - this.log('info', 'SMTP connection verification skipped') - } } export default AzureTransport diff --git a/lib/transports/SmtpTransport.js b/lib/transports/SmtpTransport.js index 755baf9..21b9c33 100644 --- a/lib/transports/SmtpTransport.js +++ b/lib/transports/SmtpTransport.js @@ -15,12 +15,7 @@ class SmtpTransport extends AbstractMailTransport { /** @override */ async test () { - try { - await nodemailer.createTransport(this.connectionUrl).verify() - this.log('info', 'SMTP connection verified successfully') - } catch (e) { - this.log('warn', `SMTP connection test failed, ${e}`) - } + await nodemailer.createTransport(this.connectionUrl).verify() } } From cc409aff8779263db3ae32222afa29160b10df32 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Thu, 16 May 2024 17:34:49 +0100 Subject: [PATCH 21/27] Add filesystem transport --- lib/transports/FilesystemTransport.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lib/transports/FilesystemTransport.js diff --git a/lib/transports/FilesystemTransport.js b/lib/transports/FilesystemTransport.js new file mode 100644 index 0000000..77a31bc --- /dev/null +++ b/lib/transports/FilesystemTransport.js @@ -0,0 +1,18 @@ +import { App } from 'adapt-authoring-core' +import AbstractMailTransport from '../AbstractMailTransport.js' +import fs from 'fs/promises' +/** + * Local filesystem shim mail transport, will store mail data locally + * @memberof mailer + * @extends {AbstractMailTransport} + */ +class FilesystemTransport extends AbstractMailTransport { + name = 'filesystem' + + /** @override */ + async send (data) { + return fs.writeFile(path.join(App.instance.getConfig('tempDir'), 'mailer', `${Date.now()}.txt`), JSON.stringify(data, null, 2)) + } +} + +export default FilesystemTransport From 42b09023165f25b2ef5647de7ac776bf276e9515 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Thu, 16 May 2024 18:07:47 +0100 Subject: [PATCH 22/27] Add config getter --- lib/AbstractMailTransport.js | 10 ++++++++++ lib/transports/AzureTransport.js | 2 +- lib/transports/SmtpTransport.js | 8 ++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/AbstractMailTransport.js b/lib/AbstractMailTransport.js index f12c41d..f44b72e 100644 --- a/lib/AbstractMailTransport.js +++ b/lib/AbstractMailTransport.js @@ -1,3 +1,4 @@ +import { App } from 'adapt-authoring-core' /** * An abstract class which encompasses functions related to a single mail transport type * @memberof mailer @@ -5,6 +6,15 @@ class AbstractMailTransport { name; + /** + * Shortcut to retrieve mailer config values + * @param {string} key + * @returns {String} the config value + */ + getConfig (key) { + return App.instance.config.get(`adapt-authoring-mailer.${key}`) + } + /** * Sends an email * @param {MailData} data diff --git a/lib/transports/AzureTransport.js b/lib/transports/AzureTransport.js index 208589d..229c6c0 100644 --- a/lib/transports/AzureTransport.js +++ b/lib/transports/AzureTransport.js @@ -21,7 +21,7 @@ class AzureTransport extends AbstractMailTransport { to: [{ address: data.to }] } } - const client = new EmailClient(this.connectionUrl) + const client = new EmailClient(this.getConfig('connectionUrl')) const poller = await client.beginSend(message) if (!poller.getOperationState().isStarted) { diff --git a/lib/transports/SmtpTransport.js b/lib/transports/SmtpTransport.js index 21b9c33..4245245 100644 --- a/lib/transports/SmtpTransport.js +++ b/lib/transports/SmtpTransport.js @@ -8,14 +8,18 @@ import nodemailer from 'nodemailer' class SmtpTransport extends AbstractMailTransport { name = 'smtp' + createTransport () { + return nodemailer.createTransport(this.getConfig('connectionUrl')) + } + /** @override */ async send (data) { - return nodemailer.createTransport(this.connectionUrl).sendMail(data) + return this.createTransport().sendMail(data) } /** @override */ async test () { - await nodemailer.createTransport(this.connectionUrl).verify() + await this.createTransport().verify() } } From 034074e4cdfd52568aea23737e7dbb5c4d7583fe Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Thu, 16 May 2024 18:54:52 +0100 Subject: [PATCH 23/27] Fix azure polling timeout --- lib/transports/AzureTransport.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/transports/AzureTransport.js b/lib/transports/AzureTransport.js index 229c6c0..4b7ffc6 100644 --- a/lib/transports/AzureTransport.js +++ b/lib/transports/AzureTransport.js @@ -27,14 +27,15 @@ class AzureTransport extends AbstractMailTransport { if (!poller.getOperationState().isStarted) { throw new Error('Poller was not started.') } - let timeElapsed = 0 + let elapsedSecs = 0 + const timeoutSecs = 10 while (!poller.isDone()) { poller.poll() await new Promise(resolve => setTimeout(resolve, 1000)) - timeElapsed += 10 + timeElapsed++ - if (timeElapsed > 18) { + if (timeElapsed >= timeoutSecs) { throw new Error('Polling timed out.') } } From afd817474ef3bc1058e2910b1dc82c66b5ccd0dd Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Fri, 17 May 2024 12:54:28 +0100 Subject: [PATCH 24/27] Remove erroneous config --- conf/config.schema.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conf/config.schema.json b/conf/config.schema.json index c33a81a..bd5a54e 100644 --- a/conf/config.schema.json +++ b/conf/config.schema.json @@ -12,8 +12,7 @@ }, "transport": { "description": "Mail transport to use", - "type": "string", - "format": "smtp" + "type": "string" }, "connectionUrl": { "description": "Connection URL for the SMTP service", From 3c18ddadeb7ec36c9e52b29a352e54bfa2460ec6 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Fri, 7 Jun 2024 18:22:46 +0100 Subject: [PATCH 25/27] Hook up FilesystemTransport --- lib/MailerModule.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/MailerModule.js b/lib/MailerModule.js index 22e91c8..f670e8a 100644 --- a/lib/MailerModule.js +++ b/lib/MailerModule.js @@ -1,6 +1,7 @@ import { AbstractModule } from 'adapt-authoring-core' import AbstractMailTransport from './AbstractMailTransport.js' import AzureTransport from './transports/AzureTransport.js' +import FilesystemTransport from './transports/FilesystemTransport.js' import SmtpTransport from './transports/SmtpTransport.js' /** * Mailer Module @@ -44,6 +45,7 @@ class MailerModule extends AbstractModule { if (this.isEnabled) { // add the standard transport this.registerTransport(AzureTransport) + this.registerTransport(FilesystemTransport) this.registerTransport(SmtpTransport) this.app.onReady().then(() => this.initTransports()) } From 653ee5a4b73cf046ee2a7a80ae8d6c683b98e68f Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Tue, 11 Jun 2024 14:28:50 +0100 Subject: [PATCH 26/27] Fix typo --- lib/transports/AzureTransport.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/transports/AzureTransport.js b/lib/transports/AzureTransport.js index 4b7ffc6..2aaa140 100644 --- a/lib/transports/AzureTransport.js +++ b/lib/transports/AzureTransport.js @@ -33,9 +33,9 @@ class AzureTransport extends AbstractMailTransport { poller.poll() await new Promise(resolve => setTimeout(resolve, 1000)) - timeElapsed++ + elapsedSecs++ - if (timeElapsed >= timeoutSecs) { + if (elapsedSecs >= timeoutSecs) { throw new Error('Polling timed out.') } } From 16f03c7d0e4422f56fa7ec198f7bb3f9fb657d86 Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Wed, 19 Jun 2024 13:18:52 +0100 Subject: [PATCH 27/27] Fix filesystem mail transport --- lib/transports/FilesystemTransport.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/transports/FilesystemTransport.js b/lib/transports/FilesystemTransport.js index 77a31bc..1363ac1 100644 --- a/lib/transports/FilesystemTransport.js +++ b/lib/transports/FilesystemTransport.js @@ -1,6 +1,7 @@ -import { App } from 'adapt-authoring-core' import AbstractMailTransport from '../AbstractMailTransport.js' +import { App } from 'adapt-authoring-core' import fs from 'fs/promises' +import path from 'path' /** * Local filesystem shim mail transport, will store mail data locally * @memberof mailer @@ -11,7 +12,11 @@ class FilesystemTransport extends AbstractMailTransport { /** @override */ async send (data) { - return fs.writeFile(path.join(App.instance.getConfig('tempDir'), 'mailer', `${Date.now()}.txt`), JSON.stringify(data, null, 2)) + const dir = path.join(App.instance.getConfig('tempDir'), 'mailer') + try { + await fs.mkdir(dir) + } catch {} + return fs.writeFile(path.join(dir, `${Date.now()}.txt`), JSON.stringify(data, null, 2)) } }