mlcl_mailer
Version:
Mailer plugin for molecuel
244 lines (224 loc) • 8.23 kB
text/typescript
'use strict';
import nodemailer = require('nodemailer');
// import fs = require('fs');
// import dkim = require('nodemailer-dkim');
import nodemailerSesTransport = require('nodemailer-ses-transport');
import uuid = require('uuid');
import async = require('async');
import fs = require('fs');
import htmlToText = require('html-to-text');
import handlebars = require('handlebars');
import hbhelpers = require('handlebars-helpers');
class mlcl_mailer {
public static loaderversion = 2; // version number
public transporter: nodemailer.Transporter; // a nodemailer object
public transports: any;
public config: any; // configurations mlcl_mailer
protected viewEngine: Exphbs; // View renderer
protected templateEngine: any; // Mail templates
protected molecuel: any; // save a copy of parent molecuel
public i18n: any;
/**
* mlcl_mailer constructor listens to queue and process jobs
* @param mlcl any
* @param config any
* @return -
*/
constructor(mlcl: any, config: any) {
this.molecuel = mlcl;
this.transports = {};
mlcl.mailer = this;
this.molecuel.on('mlcl::i18n::init:post', (i18nmod) => {
this.i18n = i18nmod;
});
// node-mailer migration 2.x backward compatibility if smtp is configured in legacy mode
// Legacy object is mlcl.config.smtp, new object is mlcl.config.mail.smtp
if (mlcl && mlcl.config && mlcl.config.smtp && mlcl.config.smtp.enabled) {
let mlclConfig: any = {};
mlclConfig.smtp = mlcl.config.smtp;
this.checkSmtpConfig(mlclConfig);
if (mlcl.config.smtp.templateDir) {
this.config.templateDir = mlcl.config.smtp.templateDir;
}
const transport = nodemailer.createTransport(this.config.smtp);
this.transports.smtp = transport;
}
// node-mailer 2.x switch smtp, ses...
else if (mlcl && mlcl.config && mlcl.config.mail && mlcl.config.mail.enabled) {
// SMTP
if (mlcl.config.mail.enabled && mlcl.config.mail.smtp) {
this.checkSmtpConfig(mlcl.config.mail);
if (mlcl.config.mail.templateDir) {
this.config.templateDir = mlcl.config.mail.templateDir;
}
const transport = nodemailer.createTransport(this.config.smtp);
this.transports.smtp = transport;
}
// Amazon SES
if (mlcl.config.mail.enabled && mlcl.config.mail.ses) {
if (mlcl.config.mail.ses.tlsUnauth) {
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
}
if (!this.config) {
this.config = {
ses: {}
};
}
this.config.ses = {};
if (mlcl.config.mail.templateDir) {
this.config.templateDir = mlcl.config.mail.templateDir;
}
this.config.ses.accessKeyId = mlcl.config.mail.ses.accessKeyId;
this.config.ses.secretAccessKey = mlcl.config.mail.ses.secretAccessKey;
this.config.ses.rateLimit = mlcl.config.mail.ses.rateLimit || 5;
this.config.ses.region = mlcl.config.mail.ses.region || 'eu-west-1';
// SESTransporter
const transport = nodemailer.createTransport(
nodemailerSesTransport(this.config.ses)
);
this.transports.ses = transport;
}
}
if (mlcl.config.mail.default && this.transports[mlcl.config.mail.default]) {
this.transporter = this.transports[mlcl.config.mail.default];
} else {
throw new Error('A default mail transport must be defined');
}
this.molecuel.emit('mlcl::mailer::init:post', this);
}
public checkSmtpConfig(config: any) {
if (config && config.smtp && config.smtp.tlsUnauth) {
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
}
if (!this.config) {
this.config = {
smtp: {}
};
}
let smtp: any = {};
smtp.host = config.smtp.host || 'localhost';
smtp.port = config.smtp.port || 25;
if (config.smtp.auth) {
smtp.auth = config.smtp.auth;
}
smtp.maxConnections = config.smtp.maxConnection || 5;
smtp.maxMessages = config.smtp.maxMessages || 100;
smtp.rateLimit = config.smtp.rateLimit || false;
smtp.secure = config.smtp.secure || false;
smtp.debug = config.smtp.debug || false;
smtp.pool = config.smtp.pool || false;
this.config.smtp = smtp;
}
/**
* sendMail with nodemailer as SMTP or SES
* @param mailoptions any
* @param callback function optional
* @return void
*/
public sendMail(mailoptions: any, callback?: Function): void {
let data = mailoptions.context;
if (mailoptions.data) {
data = mailoptions.data;
}
if (mailoptions.subject) {
data.subject = mailoptions.subject;
}
this.renderTemplate(mailoptions.template, data, (err, templatedata) => {
if (!err) {
if (templatedata.text) {
mailoptions.text = templatedata.text;
}
if (templatedata.html) {
mailoptions.html = templatedata.html;
}
if (mailoptions.subjectTemplate) {
mailoptions.subject = this.handlebarCompile(data, mailoptions.subjectTemplate);
}
let transporter = this.transporter;
if (mailoptions.transport) {
transporter = this.transports[mailoptions.transport];
delete (mailoptions.transport);
}
// send mail with defined transport object
transporter.sendMail(mailoptions, (error, info) => {
let returnInfo: any = {};
if (info && info.messageId && typeof info.messageId === 'string') {
let split = info.messageId.split('@');
returnInfo.messageId = split[0];
returnInfo.messageHost = split[1];
}
if (error) {
this.molecuel.log.error('mailer', 'Error while delivering mail',
{ messageId: returnInfo.messageId, error: error });
this.molecuel.emit('mlcl::mailer::message:error', this, mailoptions, error);
} else {
this.molecuel.log.info('mailer', 'Mail queued',
{ messageId: returnInfo.messageId });
this.molecuel.emit('mlcl::mailer::message:success', this, mailoptions, info);
}
if (callback) {
callback(error, returnInfo, mailoptions);
}
});
} else {
this.molecuel.log.error('mailer', 'Error while rendering template', err);
}
});
}
public renderTemplate(templatename, data, callback) {
this.renderHtml(templatename, data, (err, html) => {
if (err) {
callback(err);
} else {
let templates: any = {};
templates.html = html;
templates.text = this.toText(html);
callback(null, templates);
}
});
}
/**
* [renderTemplate description]
* @param {[type]} templatename [description]
* @param {[type]} data [description]
* @return {String} [description]
*/
public renderHtml(templatename, data, callback): void {
let templateDir = this.config.templateDir;
fs.readFile(templateDir + '/' + templatename + '.hbs', 'utf8', (err, templatestr) => {
if (err) {
callback(err);
} else {
try {
let htmlstring = this.handlebarCompile(data, templatestr);
callback(null, htmlstring);
} catch (e) {
callback(e);
}
}
});
}
public handlebarCompile(data, templatestr: string): string {
let handlebarsinstance = handlebars.create();
hbhelpers({ handlebars: handlebarsinstance });
let lang = data.lang;
if (!data.lang) {
lang = 'en';
}
if (this.i18n) {
let i18n = this.i18n.getLocalizationInstanceForLanguage(lang);
let translate = i18n.i18next.getFixedT(lang);
handlebarsinstance.registerHelper('translate', function (translatestring) {
let translation = translate(translatestring, data);
return translation;
});
}
let compiled = handlebarsinstance.compile(templatestr);
let htmlstring = compiled(data);
return htmlstring;
}
public toText(htmlString) {
return htmlToText.fromString(htmlString);
}
}
export = mlcl_mailer;