🐛 Fixed unsubscribe flow for comment reply emails (#15232)

refs https://github.com/TryGhost/Team/issues/1790

- Currently we go to the account settings when you click 'Manage your email preferences' in the footer of an email that informs a comment author that they received a reply.
- Related Portal changes are here: https://github.com/TryGhost/Portal/pull/255
This commit is contained in:
Simon Backx 2022-08-15 11:36:08 +02:00 committed by GitHub
parent f6a7f75465
commit a666b846e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 29 additions and 14 deletions

View File

@ -18,6 +18,9 @@ module.exports = async function unsubscribeController(req, res) {
if (query.newsletter) { if (query.newsletter) {
redirectUrl.searchParams.append('newsletter', query.newsletter); redirectUrl.searchParams.append('newsletter', query.newsletter);
} }
if (query.comments) {
redirectUrl.searchParams.append('comments', query.comments);
}
redirectUrl.searchParams.append('action', 'unsubscribe'); redirectUrl.searchParams.append('action', 'unsubscribe');
return res.redirect(302, redirectUrl.href); return res.redirect(302, redirectUrl.href);

View File

@ -234,7 +234,7 @@ module.exports = {
// static data for every recipient // static data for every recipient
const data = { const data = {
unique_id: recipient.member_uuid, unique_id: recipient.member_uuid,
unsubscribe_url: postEmailSerializer.createUnsubscribeUrl(recipient.member_uuid, newsletterUuid) unsubscribe_url: postEmailSerializer.createUnsubscribeUrl(recipient.member_uuid, {newsletterUuid})
}; };
// computed properties on recipients - TODO: better way of handling these // computed properties on recipients - TODO: better way of handling these

View File

@ -174,7 +174,7 @@
</tr> </tr>
<tr> <tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 2px"> <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-top: 2px">
<p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;"><a class="small" href="{{profileUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">Manage your email preferences</a></p> <p class="small" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; line-height: 18px; font-size: 11px; color: #738A94; font-weight: normal; margin: 0; margin-bottom: 2px;"><a class="small" href="{{profileUrl}}" style="text-decoration: underline; color: #738A94; font-size: 11px;">Unsubscribe from comment reply notifications</a></p>
</td> </td>
</tr> </tr>

View File

@ -9,5 +9,5 @@ ${data.postUrl}#ghost-comments
--- ---
Sent to ${data.toEmail} from ${data.siteDomain}. Sent to ${data.toEmail} from ${data.siteDomain}.
You can manage your notification preferences at ${data.profileUrl}.`; You can unsubscribe from these notifications at ${data.profileUrl}.`;
}; };

View File

@ -2,6 +2,7 @@ const {promises: fs} = require('fs');
const path = require('path'); const path = require('path');
const moment = require('moment'); const moment = require('moment');
const htmlToPlaintext = require('@tryghost/html-to-plaintext'); const htmlToPlaintext = require('@tryghost/html-to-plaintext');
const postEmailSerializer = require('../mega/post-email-serializer');
class CommentsServiceEmails { class CommentsServiceEmails {
constructor({config, logging, models, mailer, settingsCache, urlService, urlUtils}) { constructor({config, logging, models, mailer, settingsCache, urlService, urlUtils}) {
@ -93,7 +94,7 @@ class CommentsServiceEmails {
accentColor: this.settingsCache.get('accent_color'), accentColor: this.settingsCache.get('accent_color'),
fromEmail: this.notificationFromAddress, fromEmail: this.notificationFromAddress,
toEmail: to, toEmail: to,
profileUrl: `${this.urlUtils.getSiteUrl()}#/portal/account/profile` profileUrl: postEmailSerializer.createUnsubscribeUrl(member.get('uuid'), {comments: true})
}; };
const {html, text} = await this.renderEmailTemplate('new-comment-reply', templateData); const {html, text} = await this.renderEmailTemplate('new-comment-reply', templateData);

View File

@ -59,9 +59,11 @@ const getSite = () => {
* In case of no member uuid, generates the preview unsubscribe url - `?preview=1` * In case of no member uuid, generates the preview unsubscribe url - `?preview=1`
* *
* @param {string} uuid post uuid * @param {string} uuid post uuid
* @param {string} newsletterUuid newsletter uuid * @param {Object} [options]
* @param {string} [options.newsletterUuid] newsletter uuid
* @param {boolean} [options.comments] Unsubscribe from comment emails
*/ */
const createUnsubscribeUrl = (uuid, newsletterUuid) => { const createUnsubscribeUrl = (uuid, options = {}) => {
const siteUrl = urlUtils.getSiteUrl(); const siteUrl = urlUtils.getSiteUrl();
const unsubscribeUrl = new URL(siteUrl); const unsubscribeUrl = new URL(siteUrl);
unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/'); unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/');
@ -70,8 +72,11 @@ const createUnsubscribeUrl = (uuid, newsletterUuid) => {
} else { } else {
unsubscribeUrl.searchParams.set('preview', '1'); unsubscribeUrl.searchParams.set('preview', '1');
} }
if (newsletterUuid) { if (options.newsletterUuid) {
unsubscribeUrl.searchParams.set('newsletter', newsletterUuid); unsubscribeUrl.searchParams.set('newsletter', options.newsletterUuid);
}
if (options.comments) {
unsubscribeUrl.searchParams.set('comments', '1');
} }
return unsubscribeUrl.href; return unsubscribeUrl.href;

View File

@ -137,7 +137,7 @@
}, },
"portal": { "portal": {
"url": "https://cdn.jsdelivr.net/npm/@tryghost/portal@~{version}/umd/portal.min.js", "url": "https://cdn.jsdelivr.net/npm/@tryghost/portal@~{version}/umd/portal.min.js",
"version": "2.7" "version": "2.8"
}, },
"sodoSearch": { "sodoSearch": {
"url": "https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@~{version}/umd/sodo-search.min.js", "url": "https://cdn.jsdelivr.net/npm/@tryghost/sodo-search@~{version}/umd/sodo-search.min.js",

View File

@ -236,16 +236,22 @@ describe('Post Email Serializer', function () {
unsubscribeUrl.should.eql('https://site.com/blah/unsubscribe/?preview=1'); unsubscribeUrl.should.eql('https://site.com/blah/unsubscribe/?preview=1');
}); });
it('generates unsubscribe url with only post uuid', function () { it('generates unsubscribe url with only member uuid', function () {
sinon.stub(urlUtils, 'getSiteUrl').returns('https://site.com/blah'); sinon.stub(urlUtils, 'getSiteUrl').returns('https://site.com/blah');
const unsubscribeUrl = createUnsubscribeUrl('post-abcd'); const unsubscribeUrl = createUnsubscribeUrl('member-abcd');
unsubscribeUrl.should.eql('https://site.com/blah/unsubscribe/?uuid=post-abcd'); unsubscribeUrl.should.eql('https://site.com/blah/unsubscribe/?uuid=member-abcd');
}); });
it('generates unsubscribe url with both post and newsletter uuid', function () { it('generates unsubscribe url with both post and newsletter uuid', function () {
sinon.stub(urlUtils, 'getSiteUrl').returns('https://site.com/blah'); sinon.stub(urlUtils, 'getSiteUrl').returns('https://site.com/blah');
const unsubscribeUrl = createUnsubscribeUrl('post-abcd', 'newsletter-abcd'); const unsubscribeUrl = createUnsubscribeUrl('member-abcd', {newsletterUuid: 'newsletter-abcd'});
unsubscribeUrl.should.eql('https://site.com/blah/unsubscribe/?uuid=post-abcd&newsletter=newsletter-abcd'); unsubscribeUrl.should.eql('https://site.com/blah/unsubscribe/?uuid=member-abcd&newsletter=newsletter-abcd');
});
it('generates unsubscribe url with comments', function () {
sinon.stub(urlUtils, 'getSiteUrl').returns('https://site.com/blah');
const unsubscribeUrl = createUnsubscribeUrl('member-abcd', {comments: true});
unsubscribeUrl.should.eql('https://site.com/blah/unsubscribe/?uuid=member-abcd&comments=1');
}); });
}); });