add nodemailer + templates

This commit is contained in:
Ivan Socolsky 2015-04-29 12:10:01 -03:00
parent 12232dbe16
commit b78395b851
8 changed files with 96 additions and 42 deletions

View File

@ -39,5 +39,12 @@ var config = {
url: 'https://test-insight.bitpay.com:443', url: 'https://test-insight.bitpay.com:443',
}, },
}, },
email: {
service: 'Gmail',
auth: {
user: '',
pass: ''
}
}
}; };
module.exports = config; module.exports = config;

View File

@ -6,28 +6,25 @@ var async = require('async');
var log = require('npmlog'); var log = require('npmlog');
log.debug = log.verbose; log.debug = log.verbose;
var fs = require('fs'); var fs = require('fs');
var nodemailer = require('nodemailer');
var Model = require('./model'); var Model = require('./model');
var EMAIL_TYPES = { var EMAIL_TYPES = {
'NewTxProposal': { 'NewTxProposal': {
filename: 'new_tx_proposal', filename: 'new_tx_proposal',
notifyCreator: false,
notifyDoer: false, notifyDoer: false,
}, },
'NewOutgoingTx': { 'NewOutgoingTx': {
filename: 'new_outgoing_tx', filename: 'new_outgoing_tx',
notifyCreator: true,
notifyDoer: true, notifyDoer: true,
}, },
'NewIncomingTx': { 'NewIncomingTx': {
filename: 'new_incoming_tx', filename: 'new_incoming_tx',
notifyCreator: true,
notifyDoer: true, notifyDoer: true,
}, },
'TxProposalFinallyRejected': { 'TxProposalFinallyRejected': {
filename: 'txp_finally_rejected', filename: 'txp_finally_rejected',
notifyCreator: true,
notifyDoer: false, notifyDoer: false,
}, },
}; };
@ -36,10 +33,17 @@ var EMAIL_TYPES = {
function EmailService(opts) { function EmailService(opts) {
this.storage = opts.storage; this.storage = opts.storage;
this.lock = opts.lock; this.lock = opts.lock;
this.mailer = opts.mailer || nodemailer.createTransport(opts.email);
$.checkState(this.mailer);
}; };
// TODO: cache for X minutes
EmailService.prototype._readTemplate = function(filename, cb) { EmailService.prototype._readTemplate = function(filename, cb) {
fs.readFile(__dirname + '/templates/' + filename + '.plain', 'utf8', function(err, template) { fs.readFile(__dirname + '/templates/' + filename + '.plain', 'utf8', function(err, template) {
if (err) {
log.error('Could not read template file ' + filename, err);
return cb(err);
}
var lines = template.split('\n'); var lines = template.split('\n');
return cb(null, { return cb(null, {
subject: _.template(lines[0]), subject: _.template(lines[0]),
@ -50,20 +54,20 @@ EmailService.prototype._readTemplate = function(filename, cb) {
EmailService.prototype._applyTemplate = function(template, data, cb) { EmailService.prototype._applyTemplate = function(template, data, cb) {
var result = _.mapValues(template, function(t) { var result = _.mapValues(template, function(t) {
// TODO: If this throws exception, log and abort email generation try {
return t(data); return t(data);
} catch (e) {
log.error('Could not apply data to template', e);
return cb(e);
}
}); });
return cb(null, result); return cb(null, result);
}; };
EmailService.prototype._generateFromNotification = function(notification, cb) { EmailService.prototype._getEmailAddresses = function(walletId, cb) {
var self = this; self.storage.fetchPreferences(walletId, null, function(err, preferences) {
if (err) return cb(err);
var emailType = EMAIL_TYPES[notification.type]; if (_.isEmpty(preferences)) return cb(null, {});
if (!emailType) return cb();
self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) {
if (_.isEmpty(preferences)) return cb();
var addressesByCopayer = _.reduce(preferences, function(memo, p) { var addressesByCopayer = _.reduce(preferences, function(memo, p) {
if (p.email) { if (p.email) {
@ -72,40 +76,70 @@ EmailService.prototype._generateFromNotification = function(notification, cb) {
return memo; return memo;
}, {}); }, {});
if (_.isEmpty(addressesByCopayer)) return cb(); return cb(null, addressesByCopayer);
});
};
self._readTemplate(emailType.filename, function(err, template) { EmailService.prototype._send = function(email, cb) {
if (err) return cb(err); var self = this;
self._applyTemplate(template, notification.data, function(err, content) { var mailOptions = {
if (err) return cb(err); from: email.from,
to: email.to,
subject: email.subject,
text: email.body,
};
self.mailer.sendMail(mailOptions, function(err, result) {
if (err) {
log.error('An error occurred when trying to send email to ' + email.to, err);
return cb(err);
}
log.debug('Message sent: ', result || '');
return cb(err, result);
});
};
_.each(addressesByCopayer, function(emailAddress, copayerId) { EmailService.prototype._generateFromNotification = function(notification, cb) {
var email = Model.Email.create({ var self = this;
walletId: notification.walletId,
copayerId: copayerId, var emailType = EMAIL_TYPES[notification.type];
to: emailAddress, if (!emailType) return cb();
subject: content.subject,
body: content.body, var emailByCopayer;
});
self.storage.storeEmail(email, function(err) { async.waterfall([
return cb(err);
}); function(next) {
self._getEmailAddresses(notification.walletId, next);
},
function(emailAddresses, next) {
if (_.isEmpty(emailAddresses)) return cb();
emailByCopayer = emailAddresses;
self._readTemplate(emailType.filename, next);
},
function(template, next) {
self._applyTemplate(template, notification.data, next);
},
function(content, next) {
_.each(emailByCopayer, function(address, copayerId) {
var email = Model.Email.create({
walletId: notification.walletId,
copayerId: copayerId,
to: address,
subject: content.subject,
body: content.body,
});
self.storage.storeEmail(email, function(err) {
return next(err, email);
}); });
}); });
}); },
}); function(email, next) {
self._send(email, next);
},
], cb);
return cb(); return cb();
}; };
EmailService.prototype._send = function(cb) {
var self = this;
this.lock.runLocked('emails', cb, function() {
//self._fetchUnsentEmails();
});
};
module.exports = EmailService; module.exports = EmailService;

View File

@ -12,6 +12,7 @@ Email.create = function(opts) {
x.createdOn = Math.floor(Date.now() / 1000); x.createdOn = Math.floor(Date.now() / 1000);
x.walletId = opts.walletId; x.walletId = opts.walletId;
x.copayerId = opts.copayerId; x.copayerId = opts.copayerId;
x.from = opts.from;
x.to = opts.to; x.to = opts.to;
x.subject = opts.subject; x.subject = opts.subject;
x.body = opts.body; x.body = opts.body;
@ -27,6 +28,7 @@ Email.fromObj = function(obj) {
x.createdOn = obj.createdOn; x.createdOn = obj.createdOn;
x.walletId = obj.walletId; x.walletId = obj.walletId;
x.copayerId = obj.copayerId; x.copayerId = obj.copayerId;
x.from = obj.from;
x.to = obj.to; x.to = obj.to;
x.subject = obj.subject; x.subject = obj.subject;
x.body = obj.body; x.body = obj.body;

View File

@ -0,0 +1,3 @@
copay@copay.io
[Copay] Funds received!
Funds received on your wallet.

View File

@ -0,0 +1,3 @@
copay@copay.io
[Copay] Transaction broadcasted!
A transaction has been broadcasted.

View File

@ -1,2 +1,3 @@
New transaction proposal! copay@copay.io
[Copay] New transaction proposal!
A new transaction proposal has been created by another copayer. A new transaction proposal has been created by another copayer.

View File

@ -0,0 +1,3 @@
copay@copay.io
[Copay] A transaction proposal was rejected
A transaction proposal was rejected by your copayers.

View File

@ -32,6 +32,7 @@
"mocha-lcov-reporter": "0.0.1", "mocha-lcov-reporter": "0.0.1",
"mongodb": "^2.0.27", "mongodb": "^2.0.27",
"morgan": "*", "morgan": "*",
"nodemailer": "^1.3.4",
"npmlog": "^0.1.1", "npmlog": "^0.1.1",
"preconditions": "^1.0.7", "preconditions": "^1.0.7",
"read": "^1.0.5", "read": "^1.0.5",