Unsubscribed Members from newsletters when their email is suppressed

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

This ensures that a Member is not considered subscribed to any emails, so that
counts for newsletter recipients are correct. Eventually we will filter members
on their email suppression status but this is not implemented yet.
This commit is contained in:
Fabien "egg" O'Carroll 2022-12-05 16:56:01 +07:00 committed by Fabien 'egg' O'Carroll
parent 7567997dbf
commit 9736d942e1
5 changed files with 71 additions and 7 deletions

View File

@ -1,4 +1,4 @@
const {AbstractEmailSuppressionList, EmailSuppressionData} = require('@tryghost/email-suppression-list');
const {AbstractEmailSuppressionList, EmailSuppressionData, EmailSuppressedEvent} = require('@tryghost/email-suppression-list');
const {SpamComplaintEvent, EmailBouncedEvent} = require('@tryghost/email-events');
const DomainEvents = require('@tryghost/domain-events');
const logging = require('@tryghost/logging');
@ -103,6 +103,11 @@ class MailgunEmailSuppressionList extends AbstractEmailSuppressionList {
reason: 'bounce',
created_at: event.timestamp
});
DomainEvents.dispatch(EmailSuppressedEvent.create({
emailAddress: event.email,
emailId: event.emailId,
reason: reason
}, event.timestamp));
} catch (err) {
if (err.code !== 'ER_DUP_ENTRY') {
logging.error(err);

View File

@ -741,8 +741,9 @@ describe('EmailEventStorage', function () {
);
// Check not unsubscribed
const {body: {events: [notSpamEvent]}} = await agent.get(eventsURI);
assert.notEqual(notSpamEvent.type, 'email_complaint_event', 'This test requires a member that does not have a spam event');
const {body: {events: eventsBefore}} = await agent.get(eventsURI);
const existingSpamEvent = eventsBefore.find(event => event.type === 'email_complaint_event');
assert.equal(existingSpamEvent, null, 'This test requires a member that does not have a spam event');
events = [{
event: 'complained',
@ -772,7 +773,8 @@ describe('EmailEventStorage', function () {
await sleep(200);
// Check if event exists
const {body: {events: [spamComplaintEvent]}} = await agent.get(eventsURI);
const {body: {events: eventsAfter}} = await agent.get(eventsURI);
const spamComplaintEvent = eventsAfter.find(event => event.type === 'email_complaint_event');
assert.equal(spamComplaintEvent.type, 'email_complaint_event');
});

View File

@ -84,7 +84,41 @@ class AbstractEmailSuppressionList {
}
}
class EmailSuppressedEvent {
/**
* @readonly
* @type {{emailId: string, emailAddress: string, reason: string}}
*/
data;
/**
* @readonly
* @type {Date}
*/
timestamp;
/**
* @private
*/
constructor({emailAddress, emailId, reason, timestamp}) {
this.data = {
emailAddress,
emailId,
reason
};
this.timestamp = timestamp;
}
static create(data, timestamp) {
return new EmailSuppressedEvent({
...data,
timestamp: timestamp || new Date
});
}
}
module.exports = {
AbstractEmailSuppressionList,
EmailSuppressionData
EmailSuppressionData,
EmailSuppressedEvent
};

View File

@ -1,5 +1,5 @@
const assert = require('assert');
const {EmailSuppressionData} = require('../../lib/email-suppression-list');
const {EmailSuppressionData, EmailSuppressedEvent} = require('../../lib/email-suppression-list');
describe('EmailSuppressionData', function () {
it('Has null info when not suppressed', function () {
@ -12,7 +12,7 @@ describe('EmailSuppressionData', function () {
assert(data.suppressed === false);
assert(data.info === null);
});
it('', function () {
it('Has info when suppressed', function () {
const now = new Date();
const data = new EmailSuppressionData(true, {
reason: 'spam',
@ -24,3 +24,15 @@ describe('EmailSuppressionData', function () {
assert(data.info.timestamp === now);
});
});
describe('EmailSuppressedEvent', function () {
it('Exposes a create factory method', function () {
const event = EmailSuppressedEvent.create({
emailAddress: 'test@test.com',
emailId: '1234567890abcdef',
reason: 'spam'
});
assert(event instanceof EmailSuppressedEvent);
assert(event.timestamp);
});
});

View File

@ -16,6 +16,9 @@ const RouterController = require('./controllers/router');
const MemberController = require('./controllers/member');
const WellKnownController = require('./controllers/well-known');
const {EmailSuppressedEvent} = require('@tryghost/email-suppression-list');
const DomainEvents = require('@tryghost/domain-events');
module.exports = function MembersAPI({
tokenConfig: {
issuer,
@ -347,6 +350,14 @@ module.exports = function MembersAPI({
bus.emit('ready');
DomainEvents.subscribe(EmailSuppressedEvent, async function (event) {
const member = await memberRepository.get({email: event.data.emailAddress});
if (!member) {
return;
}
await memberRepository.update({newsletters: []}, {id: member.id});
});
return {
middleware,
getMemberDataFromMagicLinkToken,