TryGhost/Ghost

View on GitHub
ghost/core/core/server/services/email-suppression-list/MailgunEmailSuppressionList.js

Summary

Maintainability
B
4 hrs
Test Coverage
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');
const models = require('../../models');

/**
 * @typedef {object} IMailgunAPIClient
 * @prop {(email: string) => Promise<any>} removeBounce
 * @prop {(email: string) => Promise<any>} removeComplaint
 * @prop {(email: string) => Promise<any>} removeUnsubscribe
 */

class MailgunEmailSuppressionList extends AbstractEmailSuppressionList {
    /**
     * @param {object} deps
     * @param {import('bookshelf').Model} deps.Suppression
     * @param {IMailgunAPIClient} deps.apiClient
     */
    constructor(deps) {
        super();
        this.Suppression = deps.Suppression;
        this.apiClient = deps.apiClient;
    }

    async removeEmail(email) {
        try {
            await this.apiClient.removeBounce(email);
            await this.apiClient.removeComplaint(email);
            await this.apiClient.removeUnsubscribe(email);
        } catch (err) {
            logging.error(err);
            return false;
        }

        try {
            await this.Suppression.destroy({
                destroyBy: {
                    email: email
                }
            });
        } catch (err) {
            logging.error(err);
            return false;
        }

        return true;
    }

    async removeUnsubscribe(email) {
        try {
            await this.apiClient.removeUnsubscribe(email);
        } catch (err) {
            logging.error(err);
            return false;
        }
    }

    async getSuppressionData(email) {
        try {
            const model = await this.Suppression.findOne({
                email: email
            });

            if (!model) {
                return new EmailSuppressionData(false);
            }

            return new EmailSuppressionData(true, {
                timestamp: model.get('created_at'),
                reason: model.get('reason') === 'spam' ? 'spam' : 'fail'
            });
        } catch (err) {
            logging.error(err);
            return new EmailSuppressionData(false);
        }
    }

    async getBulkSuppressionData(emails) {
        if (emails.length === 0) {
            return [];
        }

        try {
            const collection = await this.Suppression.findAll({
                filter: `email:[${emails.map(email => `'${email}'`).join(',')}]`
            });

            return emails.map((email) => {
                const model = collection.models.find(m => m.get('email') === email);

                if (!model) {
                    return new EmailSuppressionData(false);
                }

                return new EmailSuppressionData(true, {
                    timestamp: model.get('created_at'),
                    reason: model.get('reason') === 'spam' ? 'spam' : 'fail'
                });
            });
        } catch (err) {
            logging.error(err);
            return emails.map(() => new EmailSuppressionData(false));
        }
    }

    async init() {
        this.Suppression = models.Suppression;
        const handleEvent = reason => async (event) => {
            try {
                if (reason === 'bounce') {
                    if (!Number.isInteger(event.error?.code)) {
                        return;
                    }
                    if (event.error.code !== 607 && event.error.code !== 605) {
                        return;
                    }
                }
                await this.Suppression.add({
                    email: event.email,
                    email_id: event.emailId,
                    reason: reason,
                    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);
                }
            }
        };
        DomainEvents.subscribe(EmailBouncedEvent, handleEvent('bounce'));
        DomainEvents.subscribe(SpamComplaintEvent, handleEvent('spam'));
    }
}

module.exports = MailgunEmailSuppressionList;