diff --git a/src/mailer.js b/src/mailer.js index a7b2bfcb3..f3424b692 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -31,6 +31,7 @@ var assert = require('assert'), config = require('./config.js'), debug = require('debug')('box:mailer'), docker = require('./docker.js').connection, + domains = require('./domains.js'), ejs = require('ejs'), nodemailer = require('nodemailer'), path = require('path'), @@ -59,12 +60,36 @@ function splatchError(error) { return util.inspect(result, { depth: null, showHidden: true }); } -function mailConfig() { - return { - from: '"Cloudron" ' - }; +// This will collect the most common details required for notification emails +function getMailConfig(callback) { + assert.strictEqual(typeof callback, 'function'); + + getAdminEmails(function (error, adminEmails) { + if (error) return callback(error); + + settings.getCloudronName(function (error, cloudronName) { + // this is not fatal + if (error) { + debug(error); + cloudronName = 'Cloudron'; + } + + domains.getAll(function (error, domains) { + if (error) return callback(error); + if (domains.length === 0) return callback('No domains configured'); + + callback(null, { + adminEmails: adminEmails, + cloudronName: cloudronName, + notificationDomain: domains[0].domain, + notificationFrom: '"' + cloudronName + '" ' + }); + }); + }); + }); } + function processQueue() { sendMails(gMailQueue); gMailQueue = [ ]; @@ -148,26 +173,19 @@ function mailUserEventToAdmins(user, event) { assert.strictEqual(typeof user, 'object'); assert.strictEqual(typeof event, 'string'); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + var adminEmails = _.difference(mailConfig.adminEmails, [ user.email ]); - adminEmails = _.difference(adminEmails, [ user.email ]); + var mailOptions = { + from: mailConfig.notificationFrom, + to: adminEmails.join(', '), + subject: util.format('[%s] %s %s', mailConfig.cloudronName, user.username || user.alternateEmail || user.email, event), + text: render('user_event.ejs', { user: user, event: event, format: 'text' }), + }; - var mailOptions = { - from: mailConfig().from, - to: adminEmails.join(', '), - subject: util.format('[%s] %s %s', cloudronName, user.username || user.alternateEmail || user.email, event), - text: render('user_event.ejs', { user: user, event: event, format: 'text' }), - }; - - enqueue(mailOptions); - }); + enqueue(mailOptions); }); } @@ -177,18 +195,15 @@ function sendInvite(user, invitor) { debug('Sending invite mail'); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); var templateData = { user: user, webadminUrl: config.adminOrigin(), setupLink: config.adminOrigin() + '/api/v1/session/account/setup.html?reset_token=' + user.resetToken, invitor: invitor, - cloudronName: cloudronName, + cloudronName: mailConfig.cloudronName, cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar' }; @@ -199,9 +214,9 @@ function sendInvite(user, invitor) { templateDataHTML.format = 'html'; var mailOptions = { - from: mailConfig().from, + from: mailConfig.notificationFrom, to: user.alternateEmail || user.email, - subject: util.format('Welcome to %s', cloudronName), + subject: util.format('Welcome to %s', mailConfig.cloudronName), text: render('welcome_user.ejs', templateDataText), html: render('welcome_user.ejs', templateDataHTML) }; @@ -216,40 +231,33 @@ function userAdded(user, inviteSent) { debug('Sending mail for userAdded %s including invite link', inviteSent ? 'not' : ''); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); - adminEmails = _.difference(adminEmails, [ user.email ]); + var adminEmails = _.difference(mailConfig.adminEmails, [ user.email ]); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + var templateData = { + user: user, + inviteLink: inviteSent ? null : config.adminOrigin() + '/api/v1/session/account/setup.html?reset_token=' + user.resetToken, + cloudronName: mailConfig.cloudronName, + cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar' + }; - var templateData = { - user: user, - inviteLink: inviteSent ? null : config.adminOrigin() + '/api/v1/session/account/setup.html?reset_token=' + user.resetToken, - cloudronName: cloudronName, - cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar' - }; + var templateDataText = JSON.parse(JSON.stringify(templateData)); + templateDataText.format = 'text'; - var templateDataText = JSON.parse(JSON.stringify(templateData)); - templateDataText.format = 'text'; + var templateDataHTML = JSON.parse(JSON.stringify(templateData)); + templateDataHTML.format = 'html'; - var templateDataHTML = JSON.parse(JSON.stringify(templateData)); - templateDataHTML.format = 'html'; + var mailOptions = { + from: mailConfig.notificationFrom, + to: adminEmails.join(', '), + subject: util.format('[%s] User %s added', mailConfig.cloudronName, user.alternateEmail || user.email), + text: render('user_added.ejs', templateDataText), + html: render('user_added.ejs', templateDataHTML) + }; - var mailOptions = { - from: mailConfig().from, - to: adminEmails.join(', '), - subject: util.format('[%s] User %s added', cloudronName, user.alternateEmail || user.email), - text: render('user_added.ejs', templateDataText), - html: render('user_added.ejs', templateDataHTML) - }; - - enqueue(mailOptions); - }); + enqueue(mailOptions); }); } @@ -275,16 +283,13 @@ function passwordReset(user) { debug('Sending mail for password reset for user %s.', user.email, user.id); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); var templateData = { user: user, resetLink: config.adminOrigin() + '/api/v1/session/password/reset.html?reset_token=' + user.resetToken, - cloudronName: cloudronName, + cloudronName: mailConfig.cloudronName, cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar' }; @@ -295,9 +300,9 @@ function passwordReset(user) { templateDataHTML.format = 'html'; var mailOptions = { - from: mailConfig().from, + from: mailConfig.notificationFrom, to: user.alternateEmail || user.email, - subject: util.format('[%s] Password Reset', cloudronName), + subject: util.format('[%s] Password Reset', mailConfig.cloudronName), text: render('password_reset.ejs', templateDataText), html: render('password_reset.ejs', templateDataHTML) }; @@ -311,24 +316,17 @@ function appDied(app) { debug('Sending mail for app %s @ %s died', app.id, app.fqdn); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + var mailOptions = { + from: mailConfig.notificationFrom, + to: config.provider() === 'caas' ? 'support@cloudron.io' : mailConfig.adminEmails.join(', '), + subject: util.format('[%s] App %s is down', mailConfig.cloudronName, app.fqdn), + text: render('app_down.ejs', { title: app.manifest.title, appFqdn: app.fqdn, format: 'text' }) + }; - var mailOptions = { - from: mailConfig().from, - to: config.provider() === 'caas' ? 'support@cloudron.io' : adminEmails.join(', '), - subject: util.format('[%s] App %s is down', cloudronName, app.fqdn), - text: render('app_down.ejs', { title: app.manifest.title, appFqdn: app.fqdn, format: 'text' }) - }; - - enqueue(mailOptions); - }); + enqueue(mailOptions); }); } @@ -337,43 +335,36 @@ function boxUpdateAvailable(hasSubscription, newBoxVersion, changelog) { assert.strictEqual(typeof newBoxVersion, 'string'); assert(util.isArray(changelog)); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + var converter = new showdown.Converter(); - var converter = new showdown.Converter(); + var templateData = { + webadminUrl: config.adminOrigin(), + newBoxVersion: newBoxVersion, + hasSubscription: hasSubscription, + changelog: changelog, + changelogHTML: changelog.map(function (e) { return converter.makeHtml(e); }), + cloudronName: mailConfig.cloudronName, + cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar' + }; - var templateData = { - webadminUrl: config.adminOrigin(), - newBoxVersion: newBoxVersion, - hasSubscription: hasSubscription, - changelog: changelog, - changelogHTML: changelog.map(function (e) { return converter.makeHtml(e); }), - cloudronName: cloudronName, - cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar' - }; + var templateDataText = JSON.parse(JSON.stringify(templateData)); + templateDataText.format = 'text'; - var templateDataText = JSON.parse(JSON.stringify(templateData)); - templateDataText.format = 'text'; + var templateDataHTML = JSON.parse(JSON.stringify(templateData)); + templateDataHTML.format = 'html'; - var templateDataHTML = JSON.parse(JSON.stringify(templateData)); - templateDataHTML.format = 'html'; + var mailOptions = { + from: mailConfig.notificationFrom, + to: mailConfig.adminEmails.join(', '), + subject: util.format('%s has a new update available', mailConfig.cloudronName), + text: render('box_update_available.ejs', templateDataText), + html: render('box_update_available.ejs', templateDataHTML) + }; - var mailOptions = { - from: mailConfig().from, - to: adminEmails.join(', '), - subject: util.format('%s has a new update available', cloudronName), - text: render('box_update_available.ejs', templateDataText), - html: render('box_update_available.ejs', templateDataHTML) - }; - - enqueue(mailOptions); - }); + enqueue(mailOptions); }); } @@ -382,129 +373,101 @@ function appUpdateAvailable(app, hasSubscription, info) { assert.strictEqual(typeof hasSubscription, 'boolean'); assert.strictEqual(typeof info, 'object'); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + var converter = new showdown.Converter(); - var converter = new showdown.Converter(); + var templateData = { + webadminUrl: config.adminOrigin(), + hasSubscription: hasSubscription, + app: app, + updateInfo: info, + changelogHTML: converter.makeHtml(info.manifest.changelog), + cloudronName: mailConfig.cloudronName, + cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar' + }; - var templateData = { - webadminUrl: config.adminOrigin(), - hasSubscription: hasSubscription, - app: app, - updateInfo: info, - changelogHTML: converter.makeHtml(info.manifest.changelog), - cloudronName: cloudronName, - cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar' - }; + var templateDataText = JSON.parse(JSON.stringify(templateData)); + templateDataText.format = 'text'; - var templateDataText = JSON.parse(JSON.stringify(templateData)); - templateDataText.format = 'text'; + var templateDataHTML = JSON.parse(JSON.stringify(templateData)); + templateDataHTML.format = 'html'; - var templateDataHTML = JSON.parse(JSON.stringify(templateData)); - templateDataHTML.format = 'html'; + var mailOptions = { + from: mailConfig.notificationFrom, + to: mailConfig.adminEmails.join(', '), + subject: util.format('App %s has a new update available', app.fqdn), + text: render('app_update_available.ejs', templateDataText), + html: render('app_update_available.ejs', templateDataHTML) + }; - var mailOptions = { - from: mailConfig().from, - to: adminEmails.join(', '), - subject: util.format('App %s has a new update available', app.fqdn), - text: render('app_update_available.ejs', templateDataText), - html: render('app_update_available.ejs', templateDataHTML) - }; - - enqueue(mailOptions); - }); + enqueue(mailOptions); }); } function sendDigest(info) { assert.strictEqual(typeof info, 'object'); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + var templateData = { + webadminUrl: config.adminOrigin(), + cloudronName: mailConfig.cloudronName, + cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar', + info: info + }; - var templateData = { - webadminUrl: config.adminOrigin(), - cloudronName: cloudronName, - cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar', - info: info - }; + var templateDataText = JSON.parse(JSON.stringify(templateData)); + templateDataText.format = 'text'; - var templateDataText = JSON.parse(JSON.stringify(templateData)); - templateDataText.format = 'text'; + var templateDataHTML = JSON.parse(JSON.stringify(templateData)); + templateDataHTML.format = 'html'; - var templateDataHTML = JSON.parse(JSON.stringify(templateData)); - templateDataHTML.format = 'html'; + var mailOptions = { + from: mailConfig.notificationFrom, + to: mailConfig.adminEmails.join(', '), + subject: util.format('[%s] Cloudron - Weekly activity digest', mailConfig.cloudronName), + text: render('digest.ejs', templateDataText), + html: render('digest.ejs', templateDataHTML) + }; - var mailOptions = { - from: mailConfig().from, - to: adminEmails.join(', '), - subject: util.format('[%s] Cloudron - Weekly activity digest', cloudronName), - text: render('digest.ejs', templateDataText), - html: render('digest.ejs', templateDataHTML) - }; - - enqueue(mailOptions); - }); + enqueue(mailOptions); }); } function outOfDiskSpace(message) { assert.strictEqual(typeof message, 'string'); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + var mailOptions = { + from: mailConfig.notificationFrom, + to: config.provider() === 'caas' ? 'support@cloudron.io' : mailConfig.adminEmails.join(', '), + subject: util.format('[%s] Out of disk space alert', mailConfig.cloudronName), + text: render('out_of_disk_space.ejs', { cloudronName: mailConfig.cloudronName, message: message, format: 'text' }) + }; - var mailOptions = { - from: mailConfig().from, - to: config.provider() === 'caas' ? 'support@cloudron.io' : adminEmails.join(', '), - subject: util.format('[%s] Out of disk space alert', cloudronName), - text: render('out_of_disk_space.ejs', { cloudronName: cloudronName, message: message, format: 'text' }) - }; - - sendMails([ mailOptions ]); - }); + sendMails([ mailOptions ]); }); } function backupFailed(error) { var message = splatchError(error); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + var mailOptions = { + from: mailConfig.notificationFrom, + to: config.provider() === 'caas' ? 'support@cloudron.io' : mailConfig.adminEmails.join(', '), + subject: util.format('[%s] Failed to backup', mailConfig.cloudronName), + text: render('backup_failed.ejs', { cloudronName: mailConfig.cloudronName, message: message, format: 'text' }) + }; - var mailOptions = { - from: mailConfig().from, - to: config.provider() === 'caas' ? 'support@cloudron.io' : adminEmails.join(', '), - subject: util.format('[%s] Failed to backup', cloudronName), - text: render('backup_failed.ejs', { cloudronName: cloudronName, message: message, format: 'text' }) - }; - - enqueue(mailOptions); - }); + enqueue(mailOptions); }); } @@ -512,12 +475,12 @@ function certificateRenewalError(domain, message) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof message, 'string'); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); var mailOptions = { - from: mailConfig().from, - to: config.provider() === 'caas' ? 'support@cloudron.io' : adminEmails.join(', '), + from: mailConfig.notificationFrom, + to: config.provider() === 'caas' ? 'support@cloudron.io' : mailConfig.adminEmails.join(', '), subject: util.format('[%s] Certificate renewal error', domain), text: render('certificate_renewal_error.ejs', { domain: domain, message: message, format: 'text' }) }; @@ -530,24 +493,17 @@ function oomEvent(program, context) { assert.strictEqual(typeof program, 'string'); assert.strictEqual(typeof context, 'string'); - getAdminEmails(function (error, adminEmails) { - if (error) return debug('Error getting admins', error); + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + var mailOptions = { + from: mailConfig.notificationFrom, + to: config.provider() === 'caas' ? 'support@cloudron.io' : mailConfig.adminEmails.join(', '), + subject: util.format('[%s] %s exited unexpectedly', mailConfig.cloudronName, program), + text: render('oom_event.ejs', { cloudronName: mailConfig.cloudronName, program: program, context: context, format: 'text' }) + }; - var mailOptions = { - from: mailConfig().from, - to: config.provider() === 'caas' ? 'support@cloudron.io' : adminEmails.join(', '), - subject: util.format('[%s] %s exited unexpectedly', cloudronName, program), - text: render('oom_event.ejs', { cloudronName: cloudronName, program: program, context: context, format: 'text' }) - }; - - sendMails([ mailOptions ]); - }); + sendMails([ mailOptions ]); }); } @@ -566,8 +522,9 @@ function unexpectedExit(program, context, callback) { cloudronName = 'Cloudron'; } + // FIXME which domain should we use here if we can't contact the db?? var mailOptions = { - from: mailConfig().from, + from: '"Cloudron" ', to: 'support@cloudron.io', subject: util.format('[%s] %s exited unexpectedly', cloudronName, program), text: render('unexpected_exit.ejs', { cloudronName: cloudronName, program: program, context: context, format: 'text' }) @@ -580,17 +537,14 @@ function unexpectedExit(program, context, callback) { function sendTestMail(email) { assert.strictEqual(typeof email, 'string'); - settings.getCloudronName(function (error, cloudronName) { - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } + getMailConfig(function (error, mailConfig) { + if (error) return debug('Error getting mail details:', error); var mailOptions = { - from: mailConfig().from, + from: mailConfig.notificationFrom, to: email, - subject: util.format('Test Email from %s', cloudronName), - text: render('test.ejs', { cloudronName: cloudronName, format: 'text'}) + subject: util.format('Test Email from %s', mailConfig.cloudronName), + text: render('test.ejs', { cloudronName: mailConfig.cloudronName, format: 'text'}) }; enqueue(mailOptions);