Skip to content
Snippets Groups Projects
Commit 8091034b authored by Andrew Newdigate's avatar Andrew Newdigate
Browse files

Feature: send emails using Amazon SES instead of crappy Mandrill

parent b183bb6d
No related branches found
No related tags found
1 merge request!8Send emails using Amazon SES instead of crappy Mandrill
'use strict';
 
var Promise = require('bluebird');
exports.create = function(options) {
var config = options.config;
var stats = options.stats;
var logger = options.logger;
var mandrillApiKey = config.get('mandrill:apiKey')
var sendTemplate;
if (mandrillApiKey) {
var mandrillClient = require('mandrill-api/mandrill');
var mandrill = new mandrillClient.Mandrill(mandrillApiKey);
/* Promisify mandrill's wierd api */
sendTemplate = function sendTemplateMandrill(options) {
return new Promise(function(resolve, reject) {
mandrill.messages.sendTemplate(options, function(result) {
var first = result[0];
if(first.status === 'rejected' || first.status === 'invalid') {
return reject(new Error('Email provider rejected email: ' + (first.reject_reason || first.status)));
}
/* Resolve */
resolve({
id: first._id,
status: first.status
});
}, function(err) {
if(!(err instanceof Error) && err.message) {
return reject(new Error(err.message));
}
reject(err);
});
});
}
} else {
// Just use a fake mailer
sendTemplate = function () {
return Promise.resolve({ fake: true });
}
}
 
/* Resolve all the values in a hash */
function resolvePromises(data) {
if(!data || typeof data !== 'object') {
return Promise.resolve(data);
}
var keys = Object.keys(data);
var values = keys.map(function(k) { return data[k]; });
return Promise.all(values)
.then(function(values) {
/* Create a copy of the data, with the resolved values */
return values.reduce(function(memo, value, index) {
var key = keys[index];
memo[key] = value;
return memo;
}, { });
});
if (config.get('amazonses:accessKeyId')) {
return require('./mailers/amazon-ses').create(options);
}
 
function convertHashToMandrillVarsArray(hash) {
return Object.keys(hash).map(function(key) {
return { name: key, content: hash[key] };
});
if (config.get('mandrill:apiKey')) {
return require('./mailers/mandrill').create(options);
}
 
/**
* Send an email and return a promise of
* { id: XXXX, status: 'queued/sent' }
*
* Required parameters:
* templateName:
* subject:
* to: (email address)
* data: a hash containing values or promises to be resolved
*/
return function(options) {
var templateName = options.templateName;
if (!templateName) throw new Error('templateName required');
/* Ensure that all promises are resolved */
return resolvePromises(options.data)
.then(function(data) {
var to = [{ email: options.to, type: 'to' }];
if(options.bcc) {
to.push({ email: options.bcc, type: 'bcc' });
}
return sendTemplate({
template_name: templateName,
template_content: convertHashToMandrillVarsArray(data),
message: {
subject: options.subject,
from_email: options.fromEmail,
from_name: options.fromName,
to: to,
tags: [options.templateName], // used for A/B testing
global_merge_vars: convertHashToMandrillVarsArray(data)
}
});
})
.then(function(result) {
/* Email sent successfully */
logger.verbose('Sent email: ' + templateName + ": " + result.id);
stats.event('email_sent');
var tracking = options.tracking;
if(tracking) {
stats.event(tracking.event, tracking.data);
}
return result;
})
.catch(function(err) {
/* Email sent failure */
logger.error('Email send failed: ' + err, { exception: err });
stats.event('email_send_failed');
throw err;
});
};
return require('./mailers/fake-mailer').create(options);
};
'use strict';
var NodeMailerMailer = require('./nodemailer-mailer');
var _ = require('lodash');
exports.create = function(options) {
var config = options.config;
var nodemailer = require('nodemailer');
var ses = require('nodemailer-ses-transport');
var transporter = nodemailer.createTransport(ses({
accessKeyId: config.get('amazonses:accessKeyId'),
secretAccessKey: config.get('amazonses:secretAccessKey')
}));
var sesMailer = new NodeMailerMailer(transporter, _.defaults({ }, options, {
defaultFromEmailName: config.get('amazonses:defaultFromEmailName'),
defaultFromEmailAddress: config.get('amazonses:defaultFromEmailAddress'),
overrideTo: config.get('amazonses:overrideTo')
}));
return function(options) {
return sesMailer.send(options);
};
};
'use strict';
var Promise = require('bluebird');
function fakeMailer() {
return Promise.resolve({ fake: true });
}
exports.create = function() {
return fakeMailer;
};
'use strict';
var Promise = require('bluebird');
var mandrillClient = require('mandrill-api/mandrill');
exports.create = function(options) {
var config = options.config;
var stats = options.stats;
var logger = options.logger;
var mandrillApiKey = config.get('mandrill:apiKey')
var mandrill = new mandrillClient.Mandrill(mandrillApiKey);
function sendTemplate(options) {
return new Promise(function(resolve, reject) {
mandrill.messages.sendTemplate(options, function(result) {
var first = result[0];
if(first.status === 'rejected' || first.status === 'invalid') {
return reject(new Error('Email provider rejected email: ' + (first.reject_reason || first.status)));
}
/* Resolve */
resolve({
id: first._id,
status: first.status
});
}, function(err) {
if(!(err instanceof Error) && err.message) {
return reject(new Error(err.message));
}
reject(err);
});
});
}
/* Resolve all the values in a hash */
function resolvePromises(data) {
if(!data || typeof data !== 'object') {
return Promise.resolve(data);
}
var keys = Object.keys(data);
var values = keys.map(function(k) { return data[k]; });
return Promise.all(values)
.then(function(values) {
/* Create a copy of the data, with the resolved values */
return values.reduce(function(memo, value, index) {
var key = keys[index];
memo[key] = value;
return memo;
}, { });
});
}
function convertHashToMandrillVarsArray(hash) {
return Object.keys(hash).map(function(key) {
return { name: key, content: hash[key] };
});
}
/**
* Send an email and return a promise of
* { id: XXXX, status: 'queued/sent' }
*
* Required parameters:
* templateName:
* subject:
* to: (email address)
* data: a hash containing values or promises to be resolved
*/
return function(options) {
var templateName = options.templateName;
if (!templateName) throw new Error('templateName required');
/* Ensure that all promises are resolved */
return resolvePromises(options.data)
.then(function(data) {
var to = [{ email: options.to, type: 'to' }];
if(options.bcc) {
to.push({ email: options.bcc, type: 'bcc' });
}
return sendTemplate({
template_name: templateName,
template_content: convertHashToMandrillVarsArray(data),
message: {
subject: options.subject,
from_email: options.fromEmail,
from_name: options.fromName,
to: to,
tags: [options.templateName], // used for A/B testing
global_merge_vars: convertHashToMandrillVarsArray(data)
}
});
})
.then(function(result) {
/* Email sent successfully */
logger.verbose('Sent email: ' + templateName + ": " + result.id);
stats.event('email_sent');
var tracking = options.tracking;
if(tracking) {
stats.event(tracking.event, tracking.data);
}
return result;
})
.catch(function(err) {
/* Email sent failure */
logger.error('Email send failed: ' + err, { exception: err });
stats.event('email_send_failed');
throw err;
});
};
};
'use strict';
var Promise = require('bluebird');
var HandlebarsMemoizer = require('./templates/handlebars-memoizer');
var path = require('path');
function NodeMailerMailer(transporter, options) {
this.transporter = transporter;
this.logger = options.logger;
this.stats = options.stats;
this.config = options.config;
this.templateDirectory = this.config.get('mailer:templateDirectory');
this.defaultFromEmailName = options.defaultFromEmailName;
this.defaultFromEmailAddress = options.defaultFromEmailAddress;
this.overrideTo = options.overrideTo;
this.handlebarsCache = new HandlebarsMemoizer();
}
NodeMailerMailer.prototype.send = Promise.method(function(options) {
var templateName = options.templateName;
if (!templateName) throw new Error('templateName required');
return Promise.try(function() {
if (options.data) return Promise.props(options.data);
return {};
})
.bind(this)
.then(function(data) {
return this.applyTemplates(templateName, data);
})
.spread(function(html, text) {
var sendOptions = this.getSendOptions(options, html, text);
return this.transporter.sendMail(sendOptions);
})
.then(function(result) {
/* Email sent successfully */
this.logger.verbose('Sent email: ' + templateName + ": " + result.id);
this.stats.event('email_sent');
var tracking = options.tracking;
if(tracking) {
this.stats.event(tracking.event, tracking.data);
}
return result;
})
.catch(function(err) {
/* Email sent failure */
this.logger.error('Email send failed: ' + err, { exception: err });
this.stats.event('email_send_failed');
throw err;
});
});
NodeMailerMailer.prototype.applyTemplates = Promise.method(function(templateName, data) {
var templateHtmlFilename = path.resolve(this.templateDirectory, templateName + '_html.hbs');
var templateTextFilename = path.resolve(this.templateDirectory, templateName + '_text.hbs');
return Promise.join(
this.handlebarsCache.get(templateHtmlFilename),
this.handlebarsCache.get(templateTextFilename),
function(htmlTemplate, textTemplate) {
return [
htmlTemplate(data),
textTemplate(data)
]
});
});
NodeMailerMailer.prototype.getSendOptions = function(options, html, text) {
return {
from: {
name: options.fromName || this.defaultFromEmailName,
address: options.fromEmail || this.defaultFromEmailAddress
},
to: [this.overrideTo || options.to],
bcc: options.bcc ? [options.bcc] : undefined,
subject: options.subject,
text: text,
html: html
};
};
module.exports = NodeMailerMailer;
'use strict';
var handlebars = require('handlebars').create();
var fs = require('fs');
var Promise = require('bluebird');
function compiler(sourceFileName) {
return Promise.fromCallback(function(callback) {
fs.readFile(sourceFileName, 'utf-8', callback);
})
.then(function(source) {
return handlebars.compile(source);
})
}
module.exports = compiler;
'use strict';
var handlebarsCompiler = require('./handlebars-compiler');
function HandlebarsMemoizer() {
this.compiled = {};
}
HandlebarsMemoizer.prototype = {
get: function(sourceFileName) {
if (this.compiled[sourceFileName]) {
return this.compiled[sourceFileName];
}
var promise = handlebarsCompiler(sourceFileName);
this.compiled[sourceFileName] = promise;
return promise;
}
};
module.exports = HandlebarsMemoizer;
Loading
Loading
@@ -29,6 +29,7 @@
"debug": "^2.2.0",
"gitter-private-cube": "^0.2.12",
"gitter-redis-sentinel-client": "^0.3.0",
"handlebars": "^4.0.6",
"heapdump": "^0.3.7",
"intercom.io": "~1.2.1",
"lodash": "^4.6.1",
Loading
Loading
@@ -39,6 +40,8 @@
"mongodb-datadog-stats": "^0.1.2",
"nconf": "^0.6.9",
"node-statsd": "^0.1.1",
"nodemailer": "^2.7.0",
"nodemailer-ses-transport": "^1.5.0",
"raven": "^0.8.1",
"redis": "^0.10.1",
"response-time": "^2.3.1",
Loading
Loading
{
"amazonses": {
"accessKeyId": "AKIAJYV4XCWUB5ZQ2TKQ",
"secretAccessKey": "",
"defaultFromEmailName": "Gitter",
"defaultFromEmailAddress": "support@gitter.im",
"overrideTo": "andrew@gitter.im"
},
"mailer": {
"templateDirectory": "templates/"
}
}
"use strict";
var env = require('../../..').create(__dirname);
var mailer = env.mailer;
return mailer({
templateName: 'text',
subject: 'Hi There'
})
.then(function(result) {
console.log(result);
})
.catch(function(err) {
console.log(err.stack);
});
HELLO
HELLO
{
"amazonses": {
"accessKeyId": "AKIAJYV4XCWUB5ZQ2TKQ",
"secretAccessKey": "",
"defaultFromEmail": "support@gitter.im",
"overrideTo": "andrew@gitter.im"
},
"mailer": {
"templateDirectory": "templates/"
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment