diff --git a/lib/mailer.js b/lib/mailer.js index 331ddcef..2310662c 100644 --- a/lib/mailer.js +++ b/lib/mailer.js @@ -116,7 +116,7 @@ function getTemplate(template, callback) { } function createMailer(callback) { - settings.list(['smtpHostname', 'smtpPort', 'smtpEncryption', 'smtpUser', 'smtpPass', 'smtpLog', 'smtpDisableAuth', 'smtpMaxConnections', 'smtpMaxMessages', 'smtpSelfSigned', 'pgpPrivateKey', 'pgpPassphrase'], (err, configItems) => { + settings.list(['smtpHostname', 'smtpPort', 'smtpEncryption', 'smtpUser', 'smtpPass', 'smtpLog', 'smtpDisableAuth', 'smtpMaxConnections', 'smtpMaxMessages', 'smtpSelfSigned', 'pgpPrivateKey', 'pgpPassphrase', 'smtpThrottling'], (err, configItems) => { if (err) { return callback(err); } @@ -126,6 +126,7 @@ function createMailer(callback) { oldListeners = module.exports.transport.listeners('idle'); module.exports.transport.removeAllListeners('idle'); module.exports.transport.removeAllListeners('stream'); + module.exports.transport.checkThrottling = null; } module.exports.transport = nodemailer.createTransport({ @@ -160,6 +161,27 @@ function createMailer(callback) { oldListeners.forEach(listener => module.exports.transport.on('idle', listener)); } + let throttling = Number(configItems.smtpThrottling) || 0; + if (throttling) { + // convert to messages/second + throttling = 1 / (throttling / (3600 * 1000)); + } + let lastCheck = Date.now(); + module.exports.transport.checkThrottling = function (next) { + if (!throttling) { + return next(); + } + let nextCheck = Date.now(); + let checkDiff = (nextCheck - lastCheck); + lastCheck = nextCheck; + if (checkDiff < throttling) { + log.verbose('Mail', 'Throttling next message in %s sec.', (throttling - checkDiff) / 1000); + setTimeout(next, throttling - checkDiff); + } else { + next(); + } + }; + caches.cache.delete('sender queue'); return callback(null, module.exports.transport); }); diff --git a/package.json b/package.json index 19412677..391b21b2 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "grunt": "^1.0.1", "grunt-cli": "^1.2.0", "grunt-contrib-nodeunit": "^1.0.0", - "grunt-eslint": "^18.1.0" + "grunt-eslint": "^19.0.0" }, "dependencies": { "bcrypt-nodejs": "0.0.3", @@ -41,7 +41,7 @@ "csv-parse": "^1.1.1", "escape-html": "^1.0.3", "express": "^4.14.0", - "express-session": "^1.13.0", + "express-session": "^1.14.0", "faker": "^3.1.0", "feedparser": "^1.1.4", "geoip-ultralight": "^0.1.4", @@ -52,7 +52,7 @@ "humanize": "0.0.9", "is-url": "^1.2.1", "isemail": "^2.2.0", - "jsdom": "^9.3.0", + "jsdom": "^9.4.0", "juice": "^2.0.0", "mkdirp": "^0.5.1", "moment-timezone": "^0.5.4", diff --git a/public/javascript/editor.js b/public/javascript/editor.js index 5491abe9..0967b35e 100644 --- a/public/javascript/editor.js +++ b/public/javascript/editor.js @@ -9,14 +9,15 @@ $('.summernote').summernote({ tabsize: 2 }); -$('div.code-editor').each(function() { +$('div.code-editor').each(function () { var editor = ace.edit(this); var textarea = document.querySelector('input[name=html]'); + editor.setTheme('ace/theme/chrome'); editor.getSession().setMode('ace/mode/html'); editor.getSession().setUseWrapMode(true); editor.getSession().setUseSoftTabs(true); - editor.getSession().on('change', function() { + editor.getSession().on('change', function () { textarea.value = editor.getSession().getValue(); }); textarea.value = editor.getSession().getValue(); diff --git a/routes/settings.js b/routes/settings.js index 8f7b9d81..1059960b 100644 --- a/routes/settings.js +++ b/routes/settings.js @@ -11,7 +11,7 @@ let url = require('url'); let settings = require('../lib/models/settings'); -let allowedKeys = ['service_url', 'smtp_hostname', 'smtp_port', 'smtp_encryption', 'smtp_disable_auth', 'smtp_user', 'smtp_pass', 'admin_email', 'smtp_log', 'smtp_max_connections', 'smtp_max_messages', 'smtp_self_signed', 'default_from', 'default_address', 'default_subject', 'default_homepage', 'default_postaddress', 'default_sender', 'verp_hostname', 'verp_use', 'disable_wysiwyg', 'pgp_private_key', 'pgp_passphrase', 'ua_code', 'shoutout', 'disable_confirmations']; +let allowedKeys = ['service_url', 'smtp_hostname', 'smtp_port', 'smtp_encryption', 'smtp_disable_auth', 'smtp_user', 'smtp_pass', 'admin_email', 'smtp_log', 'smtp_max_connections', 'smtp_max_messages', 'smtp_self_signed', 'default_from', 'default_address', 'default_subject', 'default_homepage', 'default_postaddress', 'default_sender', 'verp_hostname', 'verp_use', 'disable_wysiwyg', 'pgp_private_key', 'pgp_passphrase', 'ua_code', 'shoutout', 'disable_confirmations', 'smtp_throttling']; router.all('/*', (req, res, next) => { if (!req.user) { diff --git a/routes/templates.js b/routes/templates.js index 6e2bbf84..60df77ab 100644 --- a/routes/templates.js +++ b/routes/templates.js @@ -65,6 +65,7 @@ router.get('/create', passport.csrfProtection, (req, res, next) => { data.html = data.html || rendererHtml(configItems); data.text = data.text || rendererText(configItems); data.disableWysiwyg = configItems.disableWysiwyg; + res.render('templates/create', data); }); }); diff --git a/services/sender.js b/services/sender.js index 603b5ce2..626cd231 100644 --- a/services/sender.js +++ b/services/sender.js @@ -430,12 +430,12 @@ let sendLoop = () => { }); }; setImmediate(trySend); - setImmediate(getNext); + setImmediate(() => mailer.transport.checkThrottling(getNext)); }); }); }; - mailer.transport.on('idle', getNext); + mailer.transport.on('idle', () => mailer.transport.checkThrottling(getNext)); }); }; diff --git a/views/settings.hbs b/views/settings.hbs index abff44d3..ac9b96ce 100644 --- a/views/settings.hbs +++ b/views/settings.hbs @@ -194,6 +194,16 @@ Advanced SMTP settings +
Merge tags are tags that are replaced before sending out the message. The format of the merge tag is the following: [TAG_NAME]
or [TAG_NAME/fallback]
where fallback
is an optional text value used
when TAG_NAME
is empty.
@@ -69,12 +70,14 @@
[EMAIL]
– email address of the subscriber
+ [FIRST_NAME]
– first name of the subscriber
[LINK_BROWSER]
– URL to preview the message in a browser
[SUBSCRIPTION_ID]
– Unique ID that identifies the recipient
+ [LIST_ID]
– Unique ID that identifies the list used for this campaign
+ [CAMPAIGN_ID]
– Unique ID that identifies current campaign
+ In addition to that any custom field can have its own merge tag.