'use strict'; const nodemailer = require('nodemailer'), Email = require('email-templates'), hbs = require('handlebars'), assert = require('assert'), fs = require('fs-extra'), cloneDeep = require('clone-deep'), path = require('path'), utils = require('./utils'), env = require('./env'), { Errors, DEFAULT_LANG } = require('./constants'); const { AppInputError } = require('./app_error'); const TemplateType = Object.freeze({ PUG: 'pug', // default HANDLEBARS: 'hbs', }); const Templates = Object.freeze({ RESET_PASSWORD: 'reset-password', PASSWORD_RESET: 'password-reset', UPDATE_PAYMENT: 'update-payment', UPDATE_BILL_ADDRESS: 'update-address', CURRENT_SUBSCRIPTIONS: 'current-subscriptions', SUB_RENEWAL_REMIND: 'sub-renewal-remind', SUB_TRIAL_END_REMIND: 'sub-trial-end-remind', }); const transporter = nodemailer.createTransport({ host: env.SMTP_HOST, port: env.SMTP_PORT, secure: env.SMTP_SECURE, auth: { user: env.SMTP_USR, pass: env.SMTP_PWD }, // debug: true, // logger: true }); function registerHbsPartials(partialsPath) { // Ref handlebars with vars in templates: https://github.com/forwardemail/email-templates/issues/283 // hbs is your own instance of handlebars, with partials // Register all available partials under the emails' partials folder const partialDir = path.join(process.cwd() + partialsPath); // const partialFiles = fs.readdir(partialDir, (err, partialFiles) => { if (!err && !utils.isEmptyArray(partialFiles)) { for (let i = 0; i < partialFiles.length; i++) { const pfile = partialFiles[i]; hbs.registerPartial(path.basename(pfile, '.hbs'), require(path.join(partialDir, pfile))); } // Override consolidate's handlebars instance // email.config.views.options.engineSource.requires.handlebars = hbs; } }); } registerHbsPartials('/emails/partials/'); hbs.registerHelper('$t', function (key, options) { // Translation helper to translate message by key or with hash return options.data.root.t({ phrase: key, locale: options.data.root.locale }, options.hash); }); require('handlebars-helpers')(['comparison']); const agmMailSig = ['Best,', 'AgMision Team']; const debugging = false; const defaultMailOps = { transport: transporter, preview: debugging, send: true, i18n: { locales: ['en', 'es', 'pt'] } }; function init(ops) { if (ops) { Object.assign({}, defaultMailOps, ops); } } function _toLocale(locals) { if (!utils.isEmptyObj(locals) && !utils.hasProperty(locals, 'locale')) { const _locals = cloneDeep(locals); _locals.locale = _locals.lang || DEFAULT_LANG; delete _locals.lang; return _locals; } return locals; } /** * Send an email with given template and additional info * @param {*} template the template name * @param {*} locals additional metadata * @returns */ async function sendMail(template, locals, to, from) { const email = await createEmail({ from: from, to: to }); return email.send({ template: template, // message: { // to: to, // }, locals: _toLocale(locals) }); } /** * Send a text email from to with the mail options * @param {*} contentOps { subject: , text: } */ async function sendTextMail(contentOps, to, cc) { if (utils.isEmptyObj(contentOps)) return; const _mailOps = Object.assign(contentOps, { from: 'AgMission ', to: to || 'agm_admin@agnav.com' }); if (cc) _mailOps.cc = cc; return transporter.sendMail(_mailOps); } /** * Create an Email instance * @param {*} emailOps { from, to } */ async function createEmail(emailOps, templateType = TemplateType.PUG) { let _ops = Object.assign( { message: { from: emailOps && emailOps.from || 'AgMission ' } }, defaultMailOps); if (templateType === 'hbs') { _ops = { ..._ops, views: { options: { extension: 'hbs' }, engineSource: { requires: { handlebars: hbs } } }, /* For relative assets or ones located within the same server */ // juice: true, // juiceResources: { // webResources: { // images: true, // // relativeTo: path.join(process.cwd(), '/emails/assets'), // } // } } } if (emailOps && emailOps.to) { _ops.message.to = emailOps.to; } const email = new Email(_ops); return email; } async function sendHandlebarsEmail(template, locals, to, from = null) { assert(template, AppInputError.create(Errors.TEMPLATE_NOT_FOUND)); assert(to, AppInputError.create(Errors.TO_NOT_FOUND)); const email = await createEmail({ locale: 'pt', to: to }, TemplateType.HANDLEBARS); await email.send({ template: template, locals: { ..._toLocale(locals) } }); } /** * Send update payment email notification so the customer can decide to update with a new payment method to continue using services * @param {*} locals { locale: 'en', name, pmType: 'American Visa', pmEnding: '4321', hostname: 'localhost' } * @param {*} to the destination email address. * @param {*} from (Optional) the sender email address. */ async function sendUpdatePaymentEmail(locals, to, from) { assert(locals && locals.name && locals.hostname, AppInputError.create()); locals.updatePmUrl = `https://${locals.hostname}/update-pm`; await sendHandlebarsEmail(Templates.UPDATE_PAYMENT, locals, to, from); } /** * Send update Billing Address email notification so the customer can decide to update with a valid address (for tax purpose). * @param {*} locals { locale: 'en', userId, name, address: {line1, line2, city, state, postalCode, Country (country name)}, hostname: 'localhost' } * @param {*} to the destination email address. * @param {*} from (Optional) the sender email address. */ async function sendUpdateBillingAddressEmail(locals, to, from) { assert(locals && locals.userId && locals.name && locals.hostname && !utils.isEmptyObj(locals.address), AppInputError.create()); locals.updateAddrUrl = `https://${locals.hostname}/update-bill-address/${locals.userId}`; await sendHandlebarsEmail(Templates.UPDATE_BILL_ADDRESS, locals, to, from); } /** * Send confirmation email with current subscriptions to the customer email address (normally after any subscription updates) * @param {*} locals { locale: 'en', userId, name, package: [{ name, billCyle, startDate }], addon: [name, quantity, billCyle, startDate] } * @param {*} to the destination email address. * @param {*} from (Optional) the sender email address. */ async function sendCurSubcriptionsEmail(locals, to, from) { assert(locals && locals.name, AppInputError.create()); await sendHandlebarsEmail(Templates.CURRENT_SUBSCRIPTIONS, locals, to, from); } /** * Send upcoming renewal reminder of a subscription to the customer (auto-renewal enabled) * @param {*} locals { subName, upPmDate, cardType, cardEnding, chargeAmount, hostname: 'localhost' } * @param {*} to the destination email address. * @param {*} from (Optional) the sender email address. * */ async function sendSubRenewalRemindEmail(locals, to, from) { assert(locals && locals.name && locals.hostname, AppInputError.create()); await sendHandlebarsEmail(Templates.SUB_RENEWAL_REMIND, locals, to, from); } /** * Send trial ending reminder of a subscription to the customer (auto-renewal enabled) * @param {*} locals { subName, upPmDate, cardType, cardEnding, chargeAmount, hostname: 'localhost' } * @param {*} to the destination email address. * @param {*} from (Optional) the sender email address. * */ async function sendSubTrialEndingRemindEmail(locals, to, from) { assert(locals && locals.name && locals.hostname, AppInputError.create()); locals.manageSubUrl = `https://${locals.hostname}/manage-subscription/${locals.userId}`; await sendHandlebarsEmail(Templates.SUB_TRIAL_END_REMIND, locals, to, from); } // NOTE: For Testing only async function sendTestMail(req, res) { let sent = true; try { // const custId = '5a1d6c05ffb94e7875c68dd9'; // const updateUrl = `https://${req.hostname}/update-pm/${custId}`; // await sendHandlebarsEmail( // Templates.UPDATE_PAYMENT, // { locale: 'en', name: 'Trung Hoang', pmType: 'American Visa', pmEnding: '4321', updatePmUrl: updateUrl }, // 'trungh@agnav.com', // ); await sendHandlebarsEmail( Templates.CURRENT_SUBSCRIPTIONS, { name: "Trung Hoang", // package: [{ name: "ess_2", billCycle: "year", startDate: utils.timestampToDate(1692720405) }], // addon: [{ name: "addon_1", billCycle: "month", quantity: 1, startDate: utils.timestampToDate(1692720405) }] }, 'trungh@agnav.com, trungduyhoang@gmail.com', ); } catch (err) { console.log(err); sent = false; } res.send({ msg: `Message sent ${sent}` }).end(); } module.exports = { agmMailSig, sendMail, sendTextMail, sendUpdatePaymentEmail, sendUpdateBillingAddressEmail, sendCurSubcriptionsEmail, sendSubTrialEndingRemindEmail, sendSubRenewalRemindEmail, sendTestMail }