mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 05:37:34 +03:00
88c648636c
refs https://github.com/TryGhost/Ghost/pull/12541 - make `EventProcessor` a super-class designed to be inherited from in consumer applications for application-level implementation - helps to keep application-level concerns for event handling (eg, what to do with spam complaints) and things like application database knowledge in the consumer - removed all database knowledge from `EmailAnalyticsService` - requires a `queries` option to be passed in that lets the consuming application provide knowledge and define how fetched stats should be aggregated
131 lines
4.5 KiB
JavaScript
131 lines
4.5 KiB
JavaScript
const mailgunJs = require('mailgun-js');
|
|
const moment = require('moment');
|
|
const {EventProcessingResult} = require('@tryghost/email-analytics-service');
|
|
|
|
const EVENT_FILTER = 'delivered OR opened OR failed OR unsubscribed OR complained';
|
|
const PAGE_LIMIT = 300;
|
|
const TRUST_THRESHOLD_S = 30 * 60; // 30 minutes
|
|
const DEFAULT_TAGS = ['bulk-email'];
|
|
|
|
class EmailAnalyticsProviderMailgun {
|
|
constructor({config, settings, mailgun, logging = console}) {
|
|
this.config = config;
|
|
this.settings = settings;
|
|
this.logging = logging;
|
|
this.tags = [...DEFAULT_TAGS];
|
|
this._mailgun = mailgun;
|
|
|
|
if (this.config.get('bulkEmail:mailgun:tag')) {
|
|
this.tags.push(this.config.get('bulkEmail:mailgun:tag'));
|
|
}
|
|
}
|
|
|
|
// unless an instance is passed in to the constructor, generate a new instance each
|
|
// time the getter is called to account for changes in config/settings over time
|
|
get mailgun() {
|
|
if (this._mailgun) {
|
|
return this._mailgun;
|
|
}
|
|
|
|
const bulkEmailConfig = this.config.get('bulkEmail');
|
|
const bulkEmailSetting = {
|
|
apiKey: this.settings.get('mailgun_api_key'),
|
|
domain: this.settings.get('mailgun_domain'),
|
|
baseUrl: this.settings.get('mailgun_base_url')
|
|
};
|
|
const hasMailgunConfig = !!(bulkEmailConfig && bulkEmailConfig.mailgun);
|
|
const hasMailgunSetting = !!(bulkEmailSetting && bulkEmailSetting.apiKey && bulkEmailSetting.baseUrl && bulkEmailSetting.domain);
|
|
|
|
if (!hasMailgunConfig && !hasMailgunSetting) {
|
|
this.logging.warn(`Bulk email service is not configured`);
|
|
return undefined;
|
|
}
|
|
|
|
const mailgunConfig = hasMailgunConfig ? bulkEmailConfig.mailgun : bulkEmailSetting;
|
|
const baseUrl = new URL(mailgunConfig.baseUrl);
|
|
|
|
return mailgunJs({
|
|
apiKey: mailgunConfig.apiKey,
|
|
domain: mailgunConfig.domain,
|
|
protocol: baseUrl.protocol,
|
|
host: baseUrl.hostname,
|
|
port: baseUrl.port,
|
|
endpoint: baseUrl.pathname,
|
|
retry: 5
|
|
});
|
|
}
|
|
|
|
// do not start from a particular time, grab latest then work back through
|
|
// pages until we get a blank response
|
|
fetchAll(batchHandler) {
|
|
const options = {
|
|
event: EVENT_FILTER,
|
|
limit: PAGE_LIMIT,
|
|
tags: this.tags.join(' AND ')
|
|
};
|
|
|
|
return this._fetchPages(options, batchHandler);
|
|
}
|
|
|
|
// fetch from the last known timestamp-TRUST_THRESHOLD then work forwards
|
|
// through pages until we get a blank response. This lets us get events
|
|
// quicker than the TRUST_THRESHOLD
|
|
fetchLatest(latestTimestamp, batchHandler, options) {
|
|
const beginDate = moment(latestTimestamp).subtract(TRUST_THRESHOLD_S, 's').toDate();
|
|
|
|
const mailgunOptions = {
|
|
limit: PAGE_LIMIT,
|
|
event: EVENT_FILTER,
|
|
tags: this.tags.join(' AND '),
|
|
begin: beginDate.toUTCString(),
|
|
ascending: 'yes'
|
|
};
|
|
|
|
return this._fetchPages(mailgunOptions, batchHandler, options);
|
|
}
|
|
|
|
async _fetchPages(mailgunOptions, batchHandler, {maxEvents = Infinity} = {}) {
|
|
const {mailgun} = this;
|
|
|
|
if (!mailgun) {
|
|
this.logging.warn(`Bulk email service is not configured`);
|
|
return new EventProcessingResult();
|
|
}
|
|
|
|
const result = new EventProcessingResult();
|
|
|
|
let page = await mailgun.events().get(mailgunOptions);
|
|
let events = page && page.items && page.items.map(this.normalizeEvent) || [];
|
|
|
|
pagesLoop:
|
|
while (events.length !== 0) {
|
|
const batchResult = await batchHandler(events);
|
|
result.merge(batchResult);
|
|
|
|
if (result.totalEvents >= maxEvents) {
|
|
break pagesLoop;
|
|
}
|
|
|
|
page = await mailgun.get(page.paging.next.replace('https://api.mailgun.net/v3', ''));
|
|
events = page && page.items && page.items.map(this.normalizeEvent) || [];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
normalizeEvent(event) {
|
|
let providerId = event.message && event.message.headers && event.message.headers['message-id'];
|
|
|
|
return {
|
|
type: event.event,
|
|
severity: event.severity,
|
|
recipientEmail: event.recipient,
|
|
emailId: event['user-variables'] && event['user-variables']['email-id'],
|
|
providerId: providerId,
|
|
timestamp: new Date(event.timestamp * 1000)
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = EmailAnalyticsProviderMailgun;
|