From b78395b8514945465bbb4a0c788286c7a28fcea9 Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 29 Apr 2015 12:10:01 -0300 Subject: [PATCH] add nodemailer + templates --- config.js | 7 ++ lib/emailservice.js | 116 +++++++++++++++-------- lib/model/email.js | 2 + lib/templates/new_incoming_tx.plain | 3 + lib/templates/new_outgoing_tx.plain | 3 + lib/templates/new_tx_proposal.plain | 3 +- lib/templates/txp_finally_rejected.plain | 3 + package.json | 1 + 8 files changed, 96 insertions(+), 42 deletions(-) create mode 100644 lib/templates/new_incoming_tx.plain create mode 100644 lib/templates/new_outgoing_tx.plain create mode 100644 lib/templates/txp_finally_rejected.plain diff --git a/config.js b/config.js index e64b966..d114e60 100644 --- a/config.js +++ b/config.js @@ -39,5 +39,12 @@ var config = { url: 'https://test-insight.bitpay.com:443', }, }, + email: { + service: 'Gmail', + auth: { + user: '', + pass: '' + } + } }; module.exports = config; diff --git a/lib/emailservice.js b/lib/emailservice.js index 15e54fb..4a212c8 100644 --- a/lib/emailservice.js +++ b/lib/emailservice.js @@ -6,28 +6,25 @@ var async = require('async'); var log = require('npmlog'); log.debug = log.verbose; var fs = require('fs'); +var nodemailer = require('nodemailer'); var Model = require('./model'); var EMAIL_TYPES = { 'NewTxProposal': { filename: 'new_tx_proposal', - notifyCreator: false, notifyDoer: false, }, 'NewOutgoingTx': { filename: 'new_outgoing_tx', - notifyCreator: true, notifyDoer: true, }, 'NewIncomingTx': { filename: 'new_incoming_tx', - notifyCreator: true, notifyDoer: true, }, 'TxProposalFinallyRejected': { filename: 'txp_finally_rejected', - notifyCreator: true, notifyDoer: false, }, }; @@ -36,10 +33,17 @@ var EMAIL_TYPES = { function EmailService(opts) { this.storage = opts.storage; 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) { 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'); return cb(null, { subject: _.template(lines[0]), @@ -50,20 +54,20 @@ EmailService.prototype._readTemplate = function(filename, cb) { EmailService.prototype._applyTemplate = function(template, data, cb) { var result = _.mapValues(template, function(t) { - // TODO: If this throws exception, log and abort email generation - return t(data); + try { + return t(data); + } catch (e) { + log.error('Could not apply data to template', e); + return cb(e); + } }); return cb(null, result); }; -EmailService.prototype._generateFromNotification = function(notification, cb) { - var self = this; - - var emailType = EMAIL_TYPES[notification.type]; - if (!emailType) return cb(); - - self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) { - if (_.isEmpty(preferences)) return cb(); +EmailService.prototype._getEmailAddresses = function(walletId, cb) { + self.storage.fetchPreferences(walletId, null, function(err, preferences) { + if (err) return cb(err); + if (_.isEmpty(preferences)) return cb(null, {}); var addressesByCopayer = _.reduce(preferences, function(memo, p) { if (p.email) { @@ -72,40 +76,70 @@ EmailService.prototype._generateFromNotification = function(notification, cb) { return memo; }, {}); - if (_.isEmpty(addressesByCopayer)) return cb(); + return cb(null, addressesByCopayer); + }); +}; - self._readTemplate(emailType.filename, function(err, template) { - if (err) return cb(err); +EmailService.prototype._send = function(email, cb) { + var self = this; - self._applyTemplate(template, notification.data, function(err, content) { - if (err) return cb(err); + var mailOptions = { + 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) { - var email = Model.Email.create({ - walletId: notification.walletId, - copayerId: copayerId, - to: emailAddress, - subject: content.subject, - body: content.body, - }); - self.storage.storeEmail(email, function(err) { - return cb(err); - }); +EmailService.prototype._generateFromNotification = function(notification, cb) { + var self = this; + + var emailType = EMAIL_TYPES[notification.type]; + if (!emailType) return cb(); + + var emailByCopayer; + + async.waterfall([ + + 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(); }; -EmailService.prototype._send = function(cb) { - var self = this; - - this.lock.runLocked('emails', cb, function() { - //self._fetchUnsentEmails(); - - }); -}; - module.exports = EmailService; diff --git a/lib/model/email.js b/lib/model/email.js index f3a90ab..002facf 100644 --- a/lib/model/email.js +++ b/lib/model/email.js @@ -12,6 +12,7 @@ Email.create = function(opts) { x.createdOn = Math.floor(Date.now() / 1000); x.walletId = opts.walletId; x.copayerId = opts.copayerId; + x.from = opts.from; x.to = opts.to; x.subject = opts.subject; x.body = opts.body; @@ -27,6 +28,7 @@ Email.fromObj = function(obj) { x.createdOn = obj.createdOn; x.walletId = obj.walletId; x.copayerId = obj.copayerId; + x.from = obj.from; x.to = obj.to; x.subject = obj.subject; x.body = obj.body; diff --git a/lib/templates/new_incoming_tx.plain b/lib/templates/new_incoming_tx.plain new file mode 100644 index 0000000..51d0575 --- /dev/null +++ b/lib/templates/new_incoming_tx.plain @@ -0,0 +1,3 @@ +copay@copay.io +[Copay] Funds received! +Funds received on your wallet. diff --git a/lib/templates/new_outgoing_tx.plain b/lib/templates/new_outgoing_tx.plain new file mode 100644 index 0000000..52036b1 --- /dev/null +++ b/lib/templates/new_outgoing_tx.plain @@ -0,0 +1,3 @@ +copay@copay.io +[Copay] Transaction broadcasted! +A transaction has been broadcasted. diff --git a/lib/templates/new_tx_proposal.plain b/lib/templates/new_tx_proposal.plain index 2c39404..643f616 100644 --- a/lib/templates/new_tx_proposal.plain +++ b/lib/templates/new_tx_proposal.plain @@ -1,2 +1,3 @@ -New transaction proposal! +copay@copay.io +[Copay] New transaction proposal! A new transaction proposal has been created by another copayer. diff --git a/lib/templates/txp_finally_rejected.plain b/lib/templates/txp_finally_rejected.plain new file mode 100644 index 0000000..70cc64e --- /dev/null +++ b/lib/templates/txp_finally_rejected.plain @@ -0,0 +1,3 @@ +copay@copay.io +[Copay] A transaction proposal was rejected +A transaction proposal was rejected by your copayers. diff --git a/package.json b/package.json index 17be107..9fbc3ea 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "mocha-lcov-reporter": "0.0.1", "mongodb": "^2.0.27", "morgan": "*", + "nodemailer": "^1.3.4", "npmlog": "^0.1.1", "preconditions": "^1.0.7", "read": "^1.0.5",