UNPKG

@ima-worldhealth/sunfish

Version:

A webapp for configuring DHIS2 email reports

166 lines (141 loc) 4.31 kB
/* eslint-disable global-require */ /** * @overview mail * * @description * The mail library is responsible for composing and sending messages to * users once their report is ready. It consists of two steps: * * compose - an email object is created by taking the email body, subject, * attachments, and other data to meld them into an email template. Additionally, * variable substition is performed on the email body, such that fields can * be spliced in as needed. * * send - takes a composed email object and a list of addresses to BCC. Returns * a promise. */ const { DEVELOPERS_EMAIL, APP_EMAIL, APP_NAME, APP_URL, // if using Sendgrid SENDGRID_API_KEY, // if using a custom SMTP Server (mail in a box) SMTP_HOST, SMTP_USERNAME, SMTP_PASSWORD, } = process.env; const path = require('path'); const fs = require('fs'); const dayjs = require('dayjs'); const debug = require('debug')('sunfish:mail'); const hasSendGridCredentials = SENDGRID_API_KEY !== undefined; function setupSendGridTransport() { debug('Using SendGrid for email transport.'); const mailer = require('@sendgrid/mail'); mailer.setApiKey(SENDGRID_API_KEY); return mailer; } function setupSMTPTransport() { debug(`Using ${SMTP_HOST} for email transport.`); const nodemailer = require('nodemailer'); const transport = nodemailer.createTransport({ host : SMTP_HOST, port : 587, secure : false, auth : { user : SMTP_USERNAME, pass : SMTP_PASSWORD }, }); // check SMTP credentials transport.verify((err) => { if (err) { debug(`Error connecting to ${SMTP_HOST}.`); debug(`Error: ${JSON.stringify(err)}`); } else { debug(`${SMTP_HOST} is ready to accept connections.`); } }); // alias sendMail() as send(); transport.send = transport.sendMail; return transport; } function setupMailTransport() { return hasSendGridCredentials ? setupSendGridTransport() : setupSMTPTransport(); } const formatAsList = (file, idx) => `${idx + 1}. ${path.parse(file).name}`; const r = (str) => new RegExp(str, 'g'); /** * @function template * * @description * A micro templating engine to process email bodies. */ function template(text, data) { return text .replace(r('{{subject}}'), data.subject) .replace(r('{{date}}'), dayjs(data.date).format('DD/MM/YYYY')) .replace(r('{{app.name}}'), data.app.name) .replace(r('{{app.email}}'), data.app.email) .replace(r('{{app.url}}'), data.app.url) .replace(r('{{dashboards}}'), data.dashboards.map(formatAsList).join('\n')); } // prepares the file for SendGrid. async function prepareAttachment(filePath) { debug(`reading attachment: ${filePath}`); const file = await fs.promises.readFile(filePath); return { filename : path.parse(filePath).base, content : file.toString('base64'), type : 'application/pdf', contentType : 'application/pdf', disposition : 'attachment', }; } function prepareAttachmentSMTP(filePath) { debug(`peparing attachment: ${filePath}`); return { filename : path.parse(filePath).base, path : filePath, }; } function compose(schedule, attachments) { const { subject, body } = schedule; const data = { subject, data : new Date(), dashboards : attachments, app : { name : APP_NAME, url : APP_URL, email : APP_EMAIL, }, }; debug('templating the email into a sendable form.'); // create an email based on the template submitted by the user. return template(body, data); } // setup mail transport const mailer = setupMailTransport(); async function send(addresses, subject, body, attachments) { // remove undefined BCC addresses. const bcc = addresses.filter((addr) => !!addr); // use different attachment functions const attachmentFn = hasSendGridCredentials ? prepareAttachment : prepareAttachmentSMTP; const data = { subject, bcc, text : body, attachments : await Promise.all(attachments.map(attachmentFn)), from : SMTP_USERNAME || APP_EMAIL, to : [DEVELOPERS_EMAIL, SMTP_USERNAME], }; debug(`sending an email to ${data.to} from ${data.from} with BCCs (${data.bcc.join(',')}).`); await mailer.send(data); debug('mail sent successfully'); } exports.send = send; exports.compose = compose;