From e396219c034e95a03a5c67e0316d1612238c077f Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 26 Apr 2016 15:18:41 +0300 Subject: [PATCH 1/6] Added option to schedule sending --- lib/models/campaigns.js | 16 ++++++++--- meta.json | 2 +- routes/campaigns.js | 28 ++++++++++++++++++-- services/sender.js | 2 +- setup/sql/upgrade-00003.sql | 12 +++++++++ views/campaigns/view.hbs | 53 +++++++++++++++++++++++++++++-------- 6 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 setup/sql/upgrade-00003.sql diff --git a/lib/models/campaigns.js b/lib/models/campaigns.js index c5e0df85..84c48389 100644 --- a/lib/models/campaigns.js +++ b/lib/models/campaigns.js @@ -297,7 +297,7 @@ module.exports.delete = (id, callback) => { }); }; -module.exports.send = (id, callback) => { +module.exports.send = (id, scheduled, callback) => { module.exports.get(id, false, (err, campaign) => { if (err) { return callback(err); @@ -312,8 +312,18 @@ module.exports.send = (id, callback) => { return callback(err); } + let query; + let values; + if (scheduled) { + query = 'UPDATE campaigns SET `status`=2, `scheduled`=?, `status_change`=NOW() WHERE id=? LIMIT 1'; + values = [scheduled, id]; + } else { + query = 'UPDATE campaigns SET `status`=2, `status_change`=NOW() WHERE id=? LIMIT 1'; + values = [id]; + } + // campaigns marked as status=2 should be picked up by the sending processes - connection.query('UPDATE campaigns SET `status`=2, `status_change`=NOW() WHERE id=? LIMIT 1', [id], err => { + connection.query(query, values, err => { connection.release(); if (err) { return callback(err); @@ -357,7 +367,7 @@ module.exports.reset = (id, callback) => { return callback(err); } - if (campaign.status !== 3) { + if (campaign.status !== 3 && !(campaign.status === 2 && campaign.scheduled > new Date())) { return callback(null, false); } diff --git a/meta.json b/meta.json index 5d571180..18fc9078 100644 --- a/meta.json +++ b/meta.json @@ -1,3 +1,3 @@ { - "schemaVersion": 2 + "schemaVersion": 3 } diff --git a/routes/campaigns.js b/routes/campaigns.js index a52c6d02..3165ef38 100644 --- a/routes/campaigns.js +++ b/routes/campaigns.js @@ -38,7 +38,11 @@ router.get('/', (req, res) => { row.statusText = 'Idling'; break; case 2: - row.statusText = 'Sending'; + if (row.scheduled && row.scheduled > new Date()) { + row.statusText = 'Scheduled'; + } else { + row.statusText = 'Sending'; + } break; case 3: row.statusText = 'Finished'; @@ -228,6 +232,8 @@ router.get('/view/:id', passport.csrfProtection, (req, res) => { campaign.isFinished = campaign.status === 3; campaign.isPaused = campaign.status === 4; + campaign.isScheduled = campaign.scheduled && campaign.scheduled > new Date(); + campaign.openRate = campaign.delivered ? Math.round((campaign.opened / campaign.delivered) * 100) : 0; campaign.clicksRate = campaign.delivered ? Math.round((campaign.clicks / campaign.delivered) * 100) : 0; @@ -251,7 +257,11 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) = }); router.post('/send', passport.parseForm, passport.csrfProtection, (req, res) => { - campaigns.send(req.body.id, (err, scheduled) => { + let delayHours = Math.max(Number(req.body['delay-hours']) || 0, 0); + let delayMinutes = Math.max(Number(req.body['delay-minutes']) || 0, 0); + let scheduled = new Date(Date.now() + delayHours * 3600 * 1000 + delayMinutes * 60 * 1000); + + campaigns.send(req.body.id, scheduled, (err, scheduled) => { if (err) { req.flash('danger', err && err.message || err); } else if (scheduled) { @@ -264,6 +274,20 @@ router.post('/send', passport.parseForm, passport.csrfProtection, (req, res) => }); }); +router.post('/resume', passport.parseForm, passport.csrfProtection, (req, res) => { + campaigns.send(req.body.id, false, (err, scheduled) => { + if (err) { + req.flash('danger', err && err.message || err); + } else if (scheduled) { + req.flash('success', 'Sending resumed'); + } else { + req.flash('info', 'Could not resume sending'); + } + + return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id)); + }); +}); + router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) => { campaigns.reset(req.body.id, (err, reset) => { if (err) { diff --git a/services/sender.js b/services/sender.js index 289a3956..ff1202bb 100644 --- a/services/sender.js +++ b/services/sender.js @@ -20,7 +20,7 @@ function findUnsent(callback) { return callback(err); } - let query = 'SELECT id, list, segment FROM campaigns WHERE status=? LIMIT 1'; + let query = 'SELECT `id`, `list`, `segment` FROM `campaigns` WHERE `status`=? AND (`scheduled` IS NULL OR `scheduled` <= NOW()) LIMIT 1'; connection.query(query, [2], (err, rows) => { if (err) { connection.release(); diff --git a/setup/sql/upgrade-00003.sql b/setup/sql/upgrade-00003.sql new file mode 100644 index 00000000..897ecb7b --- /dev/null +++ b/setup/sql/upgrade-00003.sql @@ -0,0 +1,12 @@ +# Header section +# Define incrementing schema version number +SET @schema_version = '3'; + +# Adds new column 'scheduled' to campaigns table. Indicates when the sending should actually start +ALTER TABLE `campaigns` ADD COLUMN `scheduled` timestamp NULL DEFAULT NULL AFTER `status`; +CREATE INDEX schedule_index ON `campaigns` (`scheduled`); + +# Footer section +LOCK TABLES `settings` WRITE; +INSERT INTO `settings` (`key`, `value`) VALUES('db_schema_version', @schema_version) ON DUPLICATE KEY UPDATE `value`=@schema_version; +UNLOCK TABLES; diff --git a/views/campaigns/view.hbs b/views/campaigns/view.hbs index 621b35f0..04c2b805 100644 --- a/views/campaigns/view.hbs +++ b/views/campaigns/view.hbs @@ -85,10 +85,28 @@
{{#if isIdling}} -
+ +
+
+

Delay sending

+
+
+
+ +
hours
+
+
+
+
+ +
minutes
+
+
+
+ -
-
-

Sending…

+ + +
+

Sending scheduled {{scheduled}}

+ {{else}} +
+
+ + + + +
+
+

Sending…

+ {{/if}} {{/if}} {{#if isPaused}}
-
+ From a3bd7fa779d0b37c04b0a6553ed02f59a2d00b63 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 26 Apr 2016 15:46:29 +0300 Subject: [PATCH 2/6] Show meaningful MySQL errors when startup fails --- index.js | 2 +- lib/dbcheck.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index d3e1e036..565119fa 100644 --- a/index.js +++ b/index.js @@ -34,7 +34,7 @@ let server = http.createServer(app); // Check if database needs upgrading before starting the server dbcheck(err => { if (err) { - log.error('DB', err); + log.error('DB', err.message || err); return process.exit(1); } /** diff --git a/lib/dbcheck.js b/lib/dbcheck.js index 45d2be66..30b13b0c 100644 --- a/lib/dbcheck.js +++ b/lib/dbcheck.js @@ -21,6 +21,12 @@ let db = mysql.createPool(mysqlConfig); function listTables(callback) { db.getConnection((err, connection) => { if (err) { + if(err.code === 'ER_ACCESS_DENIED_ERROR'){ + err = new Error('Could not access the database. Check MySQL config and authentication credentials'); + } + if(err.code === 'ECONNREFUSED' || err.code === 'PROTOCOL_SEQUENCE_TIMEOUT'){ + err = new Error('Could not connect to the database. Check MySQL host and port configuration'); + } return callback(err); } let query = 'SHOW TABLES'; From e4c71f4026e9f20aad87ee2ab9dcde5a4b330049 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 26 Apr 2016 19:07:07 +0300 Subject: [PATCH 3/6] First take on the "send from url" feature --- README.md | 1 + lib/mailer.js | 5 ++ lib/models/campaigns.js | 2 +- meta.json | 2 +- package.json | 1 + services/sender.js | 141 +++++++++++++++++++++--------------- setup/sql/upgrade-00004.sql | 12 +++ views/campaigns/create.hbs | 32 +++++--- views/campaigns/edit.hbs | 111 +++++++++++++++------------- 9 files changed, 190 insertions(+), 117 deletions(-) create mode 100644 setup/sql/upgrade-00004.sql diff --git a/README.md b/README.md index e8380c46..738262d1 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Subscribe to Mailtrain Newsletter [here](http://mailtrain.org/subscription/EysIv ## Upgrade * Replace old files with new ones by running in the Mailtrain folder `git pull origin master` + * Run `npm install --production` in the Mailtrain folder ## Using environment variables diff --git a/lib/mailer.js b/lib/mailer.js index 763ed6ed..9ac3f861 100644 --- a/lib/mailer.js +++ b/lib/mailer.js @@ -9,6 +9,7 @@ let Handlebars = require('handlebars'); let fs = require('fs'); let path = require('path'); let templates = new Map(); +let htmlToText = require('html-to-text'); module.exports.transport = false; @@ -54,6 +55,10 @@ module.exports.sendMail = (mail, template, callback) => { if (textRenderer) { mail.text = textRenderer(template.data || {}); + } else if (mail.html) { + mail.text = htmlToText.fromString(mail.html, { + wordwrap: 130 + }); } module.exports.transport.sendMail(mail, callback); diff --git a/lib/models/campaigns.js b/lib/models/campaigns.js index 84c48389..672541df 100644 --- a/lib/models/campaigns.js +++ b/lib/models/campaigns.js @@ -8,7 +8,7 @@ let segments = require('./segments'); let subscriptions = require('./subscriptions'); let shortid = require('shortid'); -let allowedKeys = ['description', 'from', 'address', 'subject', 'template', 'list', 'segment', 'html', 'text']; +let allowedKeys = ['description', 'from', 'address', 'subject', 'template', 'template_url', 'list', 'segment', 'html', 'text']; module.exports.list = (start, limit, callback) => { db.getConnection((err, connection) => { diff --git a/meta.json b/meta.json index 18fc9078..b4a9b4a7 100644 --- a/meta.json +++ b/meta.json @@ -1,3 +1,3 @@ { - "schemaVersion": 3 + "schemaVersion": 4 } diff --git a/package.json b/package.json index 7272b941..52d7fcd8 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "geoip-ultralight": "^0.1.3", "handlebars": "^4.0.5", "hbs": "^4.0.0", + "html-to-text": "^2.1.0", "humanize": "0.0.9", "isemail": "^2.1.0", "morgan": "^1.7.0", diff --git a/services/sender.js b/services/sender.js index ff1202bb..02f8cd08 100644 --- a/services/sender.js +++ b/services/sender.js @@ -13,6 +13,8 @@ let settings = require('../lib/models/settings'); let links = require('../lib/models/links'); let shortid = require('shortid'); let url = require('url'); +let htmlToText = require('html-to-text'); +let request = require('request'); function findUnsent(callback) { db.getConnection((err, connection) => { @@ -149,73 +151,98 @@ function formatMessage(message, callback) { } }); - links.updateLinks(campaign, list, message.subscription, configItems.serviceUrl, campaign.html, (err, html) => { - if (err) { - return callback(err); - } + let renderAndSend = (html, text, renderTags) => { + links.updateLinks(campaign, list, message.subscription, configItems.serviceUrl, html, (err, html) => { + if (err) { + return callback(err); + } - // replace data: images with embedded attachments - let attachments = []; - html = html.replace(/(]* src\s*=[\s"']*)(data:[^"'>\s]+)/gi, (match, prefix, dataUri) => { - let cid = shortid.generate() + '-attachments@' + campaign.address.split('@').pop(); - attachments.push({ - path: dataUri, - cid + // replace data: images with embedded attachments + let attachments = []; + html = html.replace(/(]* src\s*=[\s"']*)(data:[^"'>\s]+)/gi, (match, prefix, dataUri) => { + let cid = shortid.generate() + '-attachments@' + campaign.address.split('@').pop(); + attachments.push({ + path: dataUri, + cid + }); + return prefix + 'cid:' + cid; }); - return prefix + 'cid:' + cid; - }); - let campaignAddress = [campaign.cid, list.cid, message.subscription.cid].join('.'); + let campaignAddress = [campaign.cid, list.cid, message.subscription.cid].join('.'); - return callback(null, { - from: { - name: campaign.from, - address: campaign.address - }, - xMailer: 'Mailtrain Mailer (+https://mailtrain.org)', - to: { - name: [].concat(message.subscription.firstName || []).concat(message.subscription.lastName || []).join(' '), - address: message.subscription.email - }, - sender: useVerp ? campaignAddress + '@' + configItems.verpHostname : false, + let renderedHtml = renderTags ? tools.formatMessage(configItems.serviceUrl, campaign, list, message.subscription, html) : html; - envelope: useVerp ? { - from: campaignAddress + '@' + configItems.verpHostname, - to: message.subscription.email - } : false, + let renderedText = (text || '').trim() ? (renderTags ? tools.formatMessage(configItems.serviceUrl, campaign, list, message.subscription, text) : text) : htmlToText.fromString(renderedHtml, { + wordwrap: 130 + }); - headers: { - 'x-fbl': campaignAddress, - // custom header for SparkPost - 'x-msys-api': JSON.stringify({ - campaign_id: campaignAddress - }), - // custom header for SendGrid - 'x-smtpapi': JSON.stringify({ - unique_args: { + return callback(null, { + from: { + name: campaign.from, + address: campaign.address + }, + xMailer: 'Mailtrain Mailer (+https://mailtrain.org)', + to: { + name: [].concat(message.subscription.firstName || []).concat(message.subscription.lastName || []).join(' '), + address: message.subscription.email + }, + sender: useVerp ? campaignAddress + '@' + configItems.verpHostname : false, + + envelope: useVerp ? { + from: campaignAddress + '@' + configItems.verpHostname, + to: message.subscription.email + } : false, + + headers: { + 'x-fbl': campaignAddress, + // custom header for SparkPost + 'x-msys-api': JSON.stringify({ campaign_id: campaignAddress + }), + // custom header for SendGrid + 'x-smtpapi': JSON.stringify({ + unique_args: { + campaign_id: campaignAddress + } + }), + // custom header for Mailgun + 'x-mailgun-variables': JSON.stringify({ + campaign_id: campaignAddress + }), + 'List-ID': { + prepared: true, + value: '"' + list.name.replace(/[^a-z0-9\s'.,\-]/g, '').trim() + '" <' + list.cid + '.' + (url.parse(configItems.serviceUrl).hostname || 'localhost') + '>' } - }), - // custom header for Mailgun - 'x-mailgun-variables': JSON.stringify({ - campaign_id: campaignAddress - }), - 'List-ID': { - prepared: true, - value: '"' + list.name.replace(/[^a-z0-9\s'.,\-]/g, '').trim() + '" <' + list.cid + '.' + (url.parse(configItems.serviceUrl).hostname || 'localhost') + '>' - } - }, - list: { - unsubscribe: url.resolve(configItems.serviceUrl, '/subscription/' + list.cid + '/unsubscribe/' + message.subscription.cid + '?auto=yes') - }, - subject: tools.formatMessage(configItems.serviceUrl, campaign, list, message.subscription, campaign.subject), - html: tools.formatMessage(configItems.serviceUrl, campaign, list, message.subscription, html), - text: tools.formatMessage(configItems.serviceUrl, campaign, list, message.subscription, campaign.text), + }, + list: { + unsubscribe: url.resolve(configItems.serviceUrl, '/subscription/' + list.cid + '/unsubscribe/' + message.subscription.cid + '?auto=yes') + }, + subject: tools.formatMessage(configItems.serviceUrl, campaign, list, message.subscription, campaign.subject), + html: renderedHtml, + text: renderedText, - attachments, - encryptionKeys + attachments, + encryptionKeys + }); }); - }); + }; + + if (!campaign.template && campaign.templateUrl) { + request.post({ + url: campaign.templateUrl, + form: message.subscription.mergeTags + }, (err, httpResponse, body) => { + if (err) { + return callback(err); + } + if (httpResponse.statusCode !== 200) { + return callback(new Error('Received status code ' + httpResponse.statusCode + ' from ' + campaign.templateUrl)); + } + renderAndSend(body && body.toString(), '', false); + }); + } else { + renderAndSend(campaign.html, campaign.text, true); + } }); }); }); diff --git a/setup/sql/upgrade-00004.sql b/setup/sql/upgrade-00004.sql new file mode 100644 index 00000000..bf2c2ef6 --- /dev/null +++ b/setup/sql/upgrade-00004.sql @@ -0,0 +1,12 @@ +# Header section +# Define incrementing schema version number +SET @schema_version = '4'; + +# Adds new column 'template_url' to campaigns table +# Indicates that this campaign should fetch message content from this URL +ALTER TABLE `campaigns` ADD COLUMN `template_url` varchar(255) CHARACTER SET ascii DEFAULT NULL AFTER `template`; + +# Footer section +LOCK TABLES `settings` WRITE; +INSERT INTO `settings` (`key`, `value`) VALUES('db_schema_version', @schema_version) ON DUPLICATE KEY UPDATE `value`=@schema_version; +UNLOCK TABLES; diff --git a/views/campaigns/create.hbs b/views/campaigns/create.hbs index 3cc86363..8b238f31 100644 --- a/views/campaigns/create.hbs +++ b/views/campaigns/create.hbs @@ -53,15 +53,29 @@
- - Not required. Creates a campaign specific copy from a template that you can later edit + +

+ Select a template: +

+
+ + Selecting a template creates a campaign specific copy from it +
+

+ Or alternatively use an URL as the message content source: +

+
+ + If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself +
+
diff --git a/views/campaigns/edit.hbs b/views/campaigns/edit.hbs index 019fa9b1..e33bcb73 100644 --- a/views/campaigns/edit.hbs +++ b/views/campaigns/edit.hbs @@ -106,60 +106,73 @@ Template 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. -

- -
    -
  • - [FIRST_NAME] – first name of the subcriber -
  • -
  • - [LAST_NAME] – last name of the subcriber -
  • -
  • - [FULL_NAME] – first and last names of the subcriber joined -
  • -
  • - [LINK_UNSUBSCRIBE] – URL that points to the preferences page of the subscriber -
  • -
  • - [LINK_PREFERENCES] – URL that points to the unsubscribe page -
  • -
  • - [LINK_BROWSER] – URL to preview the message in a browser -
  • -
-

- In addition to that any custom field can have its own merge tag. -

+ {{#if templateUrl}} +
+ +
+ + If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself
-
+ {{else}} -
- -
- {{#if disableWysiwyg}} -
{{html}}
- - {{else}} - - {{/if}} -
-
-
- -
- +
+
+ +
+

+ 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. +

+ +
    +
  • + [FIRST_NAME] – first name of the subcriber +
  • +
  • + [LAST_NAME] – last name of the subcriber +
  • +
  • + [FULL_NAME] – first and last names of the subcriber joined +
  • +
  • + [LINK_UNSUBSCRIBE] – URL that points to the preferences page of the subscriber +
  • +
  • + [LINK_PREFERENCES] – URL that points to the unsubscribe page +
  • +
  • + [LINK_BROWSER] – URL to preview the message in a browser +
  • +
+

+ In addition to that any custom field can have its own merge tag. +

+
+
-
+ +
+ +
+ {{#if disableWysiwyg}} +
{{html}}
+ + {{else}} + + {{/if}} +
+
+ +
+ +
+ +
+
+ + {{/if}}
From 43b1f1e319617ef2f65a8e87caa1b1fa47eb3304 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 26 Apr 2016 20:29:57 +0300 Subject: [PATCH 4/6] Include campaign links in form fields --- lib/tools.js | 23 +++++++++++++++-------- services/sender.js | 6 +++++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/tools.js b/lib/tools.js index 79cde0a1..6a8a98c8 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -15,7 +15,8 @@ module.exports = { createSlug, updateMenu, validateEmail, - formatMessage + formatMessage, + getMessageLinks }; function toDbKey(key) { @@ -147,17 +148,23 @@ function validateEmail(address, checkBlocked, callback) { }); } +function getMessageLinks(serviceUrl, campaign, list, subscription) { + return { + LINK_UNSUBSCRIBE: urllib.resolve(serviceUrl, '/subscription/' + list.cid + '/unsubscribe/' + subscription.cid + '?auto=yes&c=' + campaign.cid), + LINK_PREFERENCES: urllib.resolve(serviceUrl, '/subscription/' + list.cid + '/manage/' + subscription.cid), + LINK_BROWSER: urllib.resolve(serviceUrl, '/archive/' + campaign.cid + '/' + list.cid + '/' + subscription.cid) + }; +} + function formatMessage(serviceUrl, campaign, list, subscription, message, filter) { filter = typeof filter === 'function' ? filter : (str => str); + let links = getMessageLinks(serviceUrl, campaign, list, subscription); + let getValue = key => { - switch ((key || '').toString().toUpperCase().trim()) { - case 'LINK_UNSUBSCRIBE': - return urllib.resolve(serviceUrl, '/subscription/' + list.cid + '/unsubscribe/' + subscription.cid + '?auto=yes&c=' + campaign.cid); - case 'LINK_PREFERENCES': - return urllib.resolve(serviceUrl, '/subscription/' + list.cid + '/manage/' + subscription.cid); - case 'LINK_BROWSER': - return urllib.resolve(serviceUrl, '/archive/' + campaign.cid + '/' + list.cid + '/' + subscription.cid); + key = (key || '').toString().toUpperCase().trim(); + if (links.hasOwnProperty(key)) { + return links[key]; } if (subscription.mergeTags.hasOwnProperty(key)) { return subscription.mergeTags[key]; diff --git a/services/sender.js b/services/sender.js index 02f8cd08..314998b7 100644 --- a/services/sender.js +++ b/services/sender.js @@ -228,9 +228,13 @@ function formatMessage(message, callback) { }; if (!campaign.template && campaign.templateUrl) { + let form = tools.getMessageLinks(configItems.serviceUrl, campaign, list, message.subscription); + Object.keys(message.subscription.mergeTags).forEach(key => { + form[key] = message.subscription.mergeTags[key]; + }); request.post({ url: campaign.templateUrl, - form: message.subscription.mergeTags + form }, (err, httpResponse, body) => { if (err) { return callback(err); From ffcc873ef3ec38c8798ae8f988bdbfd5424fdbc8 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 26 Apr 2016 21:14:48 +0300 Subject: [PATCH 5/6] Support URL-based archive pages --- routes/archive.js | 51 ++++++++++++++++++++++++++++++++++------------ services/sender.js | 2 +- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/routes/archive.js b/routes/archive.js index 8e08e844..8ae7348f 100644 --- a/routes/archive.js +++ b/routes/archive.js @@ -7,6 +7,7 @@ let lists = require('../lib/models/lists'); let subscriptions = require('../lib/models/subscriptions'); let tools = require('../lib/tools'); let express = require('express'); +let request = require('request'); let router = new express.Router(); router.get('/:campaign/:list/:subscription', (req, res, next) => { @@ -64,21 +65,45 @@ router.get('/:campaign/:list/:subscription', (req, res, next) => { return next(err); } - // rewrite links to count clicks - links.updateLinks(campaign, list, subscription, serviceUrl, campaign.html, (err, html) => { - if (err) { - req.flash('danger', err.message || err); - return res.redirect('/'); - } + let renderAndShow = (html, renderTags) => { - res.render('archive/view', { - layout: 'archive/layout', - message: tools.formatMessage(serviceUrl, campaign, list, subscription, html), - campaign, - list, - subscription + // rewrite links to count clicks + links.updateLinks(campaign, list, subscription, serviceUrl, html, (err, html) => { + if (err) { + req.flash('danger', err.message || err); + return res.redirect('/'); + } + + res.render('archive/view', { + layout: 'archive/layout', + message: renderTags ? tools.formatMessage(serviceUrl, campaign, list, subscription, html) : html, + campaign, + list, + subscription + }); }); - }); + }; + + if (campaign.templateUrl) { + let form = tools.getMessageLinks(serviceUrl, campaign, list, subscription); + Object.keys(subscription.mergeTags).forEach(key => { + form[key] = subscription.mergeTags[key]; + }); + request.post({ + url: campaign.templateUrl, + form + }, (err, httpResponse, body) => { + if (err) { + return next(err); + } + if (httpResponse.statusCode !== 200) { + return next(new Error('Received status code ' + httpResponse.statusCode + ' from ' + campaign.templateUrl)); + } + renderAndShow(body && body.toString(), false); + }); + } else { + renderAndShow(campaign.html, true); + } }); }); }); diff --git a/services/sender.js b/services/sender.js index 314998b7..0dd18a7a 100644 --- a/services/sender.js +++ b/services/sender.js @@ -227,7 +227,7 @@ function formatMessage(message, callback) { }); }; - if (!campaign.template && campaign.templateUrl) { + if (campaign.templateUrl) { let form = tools.getMessageLinks(configItems.serviceUrl, campaign, list, message.subscription); Object.keys(message.subscription.mergeTags).forEach(key => { form[key] = message.subscription.mergeTags[key]; From 5bb0d983c651207eea0ccdc4c0349786e485a7c5 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 28 Apr 2016 10:17:43 +0300 Subject: [PATCH 6/6] added blog link --- views/index.hbs | 5 +---- views/layout.hbs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/views/index.hbs b/views/index.hbs index 55277f96..0f4c6b55 100644 --- a/views/index.hbs +++ b/views/index.hbs @@ -21,9 +21,6 @@
- - -

Open source

@@ -35,7 +32,7 @@

On the roadmap

-

Reports, API access and more…

+

Better reports, API access, campaign automation and more…

diff --git a/views/layout.hbs b/views/layout.hbs index cb30b352..dabf511b 100644 --- a/views/layout.hbs +++ b/views/layout.hbs @@ -51,6 +51,7 @@
  • {{title}}
  • {{/if}} {{/each}} +
  • Blog
  • {{#if user }} @@ -96,7 +97,6 @@ - {{#if indexPage}}