diff --git a/ghost/core/core/server/services/newsletters/NewslettersService.js b/ghost/core/core/server/services/newsletters/NewslettersService.js index 5247c69c93b..8aecf6b14b2 100644 --- a/ghost/core/core/server/services/newsletters/NewslettersService.js +++ b/ghost/core/core/server/services/newsletters/NewslettersService.js @@ -23,11 +23,12 @@ class NewslettersService { * @param {Object} options.mail * @param {Object} options.singleUseTokenProvider * @param {Object} options.urlUtils + * @param {Object} options.settingsCache * @param {ILimitService} options.limitService * @param {Object} options.emailAddressService * @param {Object} options.labs */ - constructor({NewsletterModel, MemberModel, mail, singleUseTokenProvider, urlUtils, limitService, labs, emailAddressService}) { + constructor({NewsletterModel, MemberModel, mail, singleUseTokenProvider, urlUtils, settingsCache, limitService, labs, emailAddressService}) { this.NewsletterModel = NewsletterModel; this.MemberModel = MemberModel; this.urlUtils = urlUtils; @@ -84,6 +85,7 @@ class NewslettersService { tokenProvider: singleUseTokenProvider, getSigninURL, getText, + getOpenTrackingEnabled: () => settingsCache.get('email_track_opens'), getHTML, getSubject, sentry diff --git a/ghost/core/core/server/services/newsletters/index.js b/ghost/core/core/server/services/newsletters/index.js index ef0de972268..44bb89623e1 100644 --- a/ghost/core/core/server/services/newsletters/index.js +++ b/ghost/core/core/server/services/newsletters/index.js @@ -6,6 +6,7 @@ const urlUtils = require('../../../shared/url-utils'); const limitService = require('../limits'); const labs = require('../../../shared/labs'); const emailAddressService = require('../email-address'); +const settingsCache = require('../../../shared/settings-cache'); const MAGIC_LINK_TOKEN_VALIDITY = 24 * 60 * 60 * 1000; const MAGIC_LINK_TOKEN_VALIDITY_AFTER_USAGE = 10 * 60 * 1000; @@ -22,6 +23,7 @@ module.exports = new NewslettersService({ maxUsageCount: MAGIC_LINK_TOKEN_MAX_USAGE_COUNT }), urlUtils, + settingsCache, limitService, labs, emailAddressService: emailAddressService diff --git a/ghost/core/core/server/services/settings/SettingsBREADService.js b/ghost/core/core/server/services/settings/SettingsBREADService.js index 0638920c510..a7630a5001f 100644 --- a/ghost/core/core/server/services/settings/SettingsBREADService.js +++ b/ghost/core/core/server/services/settings/SettingsBREADService.js @@ -79,6 +79,7 @@ class SettingsBREADService { transporter, tokenProvider: singleUseTokenProvider, getSigninURL, + getOpenTrackingEnabled: () => settingsCache.get('email_track_opens'), getText, getHTML, getSubject, diff --git a/ghost/core/test/e2e-api/members/send-magic-link.test.js b/ghost/core/test/e2e-api/members/send-magic-link.test.js index b177ef53d32..60ad2f236bb 100644 --- a/ghost/core/test/e2e-api/members/send-magic-link.test.js +++ b/ghost/core/test/e2e-api/members/send-magic-link.test.js @@ -377,5 +377,46 @@ describe('sendMagicLink', function () { .expectStatus(201); }); }); -}); + describe('Open tracking', function () { + it('Can enable open tracking', async function () { + settingsCache.set('email_track_opens', {value: true}); + + const email = 'open-tracking-enabled@test.com'; + await membersAgent.post('/api/send-magic-link') + .body({ + email, + emailType: 'signup' + }) + .expectEmptyBody() + .expectStatus(201); + + // Check email is sent + mockManager.assert.sentEmail({ + to: email, + subject: /Complete your sign up to Ghost!/, + 'o:tracking-opens': true + }); + }); + + it('Can disable open tracking', async function () { + settingsCache.set('email_track_opens', {value: false}); + + const email = 'open-tracking-enabled@test.com'; + await membersAgent.post('/api/send-magic-link') + .body({ + email, + emailType: 'signup' + }) + .expectEmptyBody() + .expectStatus(201); + + // Check email is sent without open tracking enabled + // TODO: This does not validate that `o:tracking-opens` is unset + mockManager.assert.sentEmail({ + to: email, + subject: /Complete your sign up to Ghost!/ + }); + }); + }); +}); diff --git a/ghost/magic-link/lib/MagicLink.js b/ghost/magic-link/lib/MagicLink.js index 91c655d9761..c5d7f52f6c3 100644 --- a/ghost/magic-link/lib/MagicLink.js +++ b/ghost/magic-link/lib/MagicLink.js @@ -30,6 +30,7 @@ class MagicLink { * @param {MailTransporter} options.transporter * @param {TokenProvider} options.tokenProvider * @param {(token: Token, type: string, referrer?: string) => URL} options.getSigninURL + * @param {() => Promise} options.getOpenTrackingEnabled * @param {typeof defaultGetText} [options.getText] * @param {typeof defaultGetHTML} [options.getHTML] * @param {typeof defaultGetSubject} [options.getSubject] @@ -42,6 +43,7 @@ class MagicLink { this.transporter = options.transporter; this.tokenProvider = options.tokenProvider; this.getSigninURL = options.getSigninURL; + this.getOpenTrackingEnabled = options.getOpenTrackingEnabled; this.getText = options.getText || defaultGetText; this.getHTML = options.getHTML || defaultGetHTML; this.getSubject = options.getSubject || defaultGetSubject; @@ -73,12 +75,18 @@ class MagicLink { const url = this.getSigninURL(token, type, options.referrer); - const info = await this.transporter.sendMail({ + const mailObject = { to: options.email, subject: this.getSubject(type), text: this.getText(url, type, options.email), html: this.getHTML(url, type, options.email) - }); + }; + + if (await this.getOpenTrackingEnabled()) { + mailObject['o:tracking-opens'] = true; + } + + const info = await this.transporter.sendMail(mailObject); return {token, info}; } diff --git a/ghost/members-api/lib/members-api.js b/ghost/members-api/lib/members-api.js index a38e86b0416..5d21441330e 100644 --- a/ghost/members-api/lib/members-api.js +++ b/ghost/members-api/lib/members-api.js @@ -157,6 +157,7 @@ module.exports = function MembersAPI({ tokenProvider, getSigninURL, getText, + getOpenTrackingEnabled: () => settingsCache.get('email_track_opens'), getHTML, getSubject, sentry