mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
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:
parent
7567997dbf
commit
9736d942e1
5 changed files with 71 additions and 7 deletions
|
@ -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 {SpamComplaintEvent, EmailBouncedEvent} = require('@tryghost/email-events');
|
||||||
const DomainEvents = require('@tryghost/domain-events');
|
const DomainEvents = require('@tryghost/domain-events');
|
||||||
const logging = require('@tryghost/logging');
|
const logging = require('@tryghost/logging');
|
||||||
|
@ -103,6 +103,11 @@ class MailgunEmailSuppressionList extends AbstractEmailSuppressionList {
|
||||||
reason: 'bounce',
|
reason: 'bounce',
|
||||||
created_at: event.timestamp
|
created_at: event.timestamp
|
||||||
});
|
});
|
||||||
|
DomainEvents.dispatch(EmailSuppressedEvent.create({
|
||||||
|
emailAddress: event.email,
|
||||||
|
emailId: event.emailId,
|
||||||
|
reason: reason
|
||||||
|
}, event.timestamp));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code !== 'ER_DUP_ENTRY') {
|
if (err.code !== 'ER_DUP_ENTRY') {
|
||||||
logging.error(err);
|
logging.error(err);
|
||||||
|
|
|
@ -741,8 +741,9 @@ describe('EmailEventStorage', function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check not unsubscribed
|
// Check not unsubscribed
|
||||||
const {body: {events: [notSpamEvent]}} = await agent.get(eventsURI);
|
const {body: {events: eventsBefore}} = await agent.get(eventsURI);
|
||||||
assert.notEqual(notSpamEvent.type, 'email_complaint_event', 'This test requires a member that does not have a spam event');
|
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 = [{
|
events = [{
|
||||||
event: 'complained',
|
event: 'complained',
|
||||||
|
@ -772,7 +773,8 @@ describe('EmailEventStorage', function () {
|
||||||
await sleep(200);
|
await sleep(200);
|
||||||
|
|
||||||
// Check if event exists
|
// 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');
|
assert.equal(spamComplaintEvent.type, 'email_complaint_event');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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 = {
|
module.exports = {
|
||||||
AbstractEmailSuppressionList,
|
AbstractEmailSuppressionList,
|
||||||
EmailSuppressionData
|
EmailSuppressionData,
|
||||||
|
EmailSuppressedEvent
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const {EmailSuppressionData} = require('../../lib/email-suppression-list');
|
const {EmailSuppressionData, EmailSuppressedEvent} = require('../../lib/email-suppression-list');
|
||||||
|
|
||||||
describe('EmailSuppressionData', function () {
|
describe('EmailSuppressionData', function () {
|
||||||
it('Has null info when not suppressed', function () {
|
it('Has null info when not suppressed', function () {
|
||||||
|
@ -12,7 +12,7 @@ describe('EmailSuppressionData', function () {
|
||||||
assert(data.suppressed === false);
|
assert(data.suppressed === false);
|
||||||
assert(data.info === null);
|
assert(data.info === null);
|
||||||
});
|
});
|
||||||
it('', function () {
|
it('Has info when suppressed', function () {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const data = new EmailSuppressionData(true, {
|
const data = new EmailSuppressionData(true, {
|
||||||
reason: 'spam',
|
reason: 'spam',
|
||||||
|
@ -24,3 +24,15 @@ describe('EmailSuppressionData', function () {
|
||||||
assert(data.info.timestamp === now);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -16,6 +16,9 @@ const RouterController = require('./controllers/router');
|
||||||
const MemberController = require('./controllers/member');
|
const MemberController = require('./controllers/member');
|
||||||
const WellKnownController = require('./controllers/well-known');
|
const WellKnownController = require('./controllers/well-known');
|
||||||
|
|
||||||
|
const {EmailSuppressedEvent} = require('@tryghost/email-suppression-list');
|
||||||
|
const DomainEvents = require('@tryghost/domain-events');
|
||||||
|
|
||||||
module.exports = function MembersAPI({
|
module.exports = function MembersAPI({
|
||||||
tokenConfig: {
|
tokenConfig: {
|
||||||
issuer,
|
issuer,
|
||||||
|
@ -347,6 +350,14 @@ module.exports = function MembersAPI({
|
||||||
|
|
||||||
bus.emit('ready');
|
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 {
|
return {
|
||||||
middleware,
|
middleware,
|
||||||
getMemberDataFromMagicLinkToken,
|
getMemberDataFromMagicLinkToken,
|
||||||
|
|
Loading…
Add table
Reference in a new issue