Ghost/ghost/email-analytics-provider-mailgun/lib/EmailAnalyticsProviderMailgun.js
Steve Larson a47298a75c
Reimplemented email analytics prioritizing email opens (#20914)
ref https://github.com/TryGhost/Ghost/pull/20835
- reimplemented email analytics changes that prioritized opened events
over other events in order to speed up open analytics
- added db persistence to fetch missing job to ensure we re-fetch every
window of events, especially important if we restart following a large
email batch

We learned a few things with the previous trial run of this. Namely,
that event throughput is not as high as we initially saw in the data for
particularly large databases. This set of changes is more conservative,
while a touch more complicated, in ensuring we capture edge cases for
really large newsletter sends (100k+ members).

In general, we want to make sure we're fetching new open events at least
every 5 mins, and often much faster than that, unless it's a quiet
period (suggesting we haven't had a newsletter send or much outstanding
event data).
2024-09-05 08:10:07 -05:00

63 lines
2.4 KiB
JavaScript

const MailgunClient = require('@tryghost/mailgun-client');
const DEFAULT_EVENT_FILTER = 'delivered OR opened OR failed OR unsubscribed OR complained';
const PAGE_LIMIT = 300;
const DEFAULT_TAGS = ['bulk-email'];
class EmailAnalyticsProviderMailgun {
mailgunClient;
constructor({config, settings}) {
this.mailgunClient = new MailgunClient({config, settings});
this.tags = [...DEFAULT_TAGS];
if (config.get('bulkEmail:mailgun:tag')) {
this.tags.push(config.get('bulkEmail:mailgun:tag'));
}
}
/**
* 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
*
* @param {Function} batchHandler
* @param {Object} [options]
* @param {Number} [options.maxEvents] Not a strict maximum. We stop fetching after we reached the maximum AND received at least one event after begin (not equal) to prevent deadlocks.
* @param {Date} [options.begin]
* @param {Date} [options.end]
* @param {String[]} [options.events]
*/
fetchLatest(batchHandler, options) {
const mailgunOptions = {
limit: PAGE_LIMIT,
event: options?.events ? options.events.join(' OR ') : DEFAULT_EVENT_FILTER,
tags: this.tags.join(' AND '),
begin: options.begin ? options.begin.getTime() / 1000 : undefined,
end: options.end ? options.end.getTime() / 1000 : undefined,
ascending: 'yes'
};
return this.#fetchAnalytics(mailgunOptions, batchHandler, {
maxEvents: options.maxEvents
});
}
/**
* @param {Object} mailgunOptions
* @param {Number} mailgunOptions.limit
* @param {String} mailgunOptions.event
* @param {String} mailgunOptions.tags
* @param {String} [mailgunOptions.begin]
* @param {String} [mailgunOptions.ascending]
* @param {Function} batchHandler
* @param {Object} [options]
* @param {Number} [options.maxEvents] Not a strict maximum. We stop fetching after we reached the maximum AND received at least one event after begin (not equal) to prevent deadlocks.
*/
async #fetchAnalytics(mailgunOptions, batchHandler, options) {
return await this.mailgunClient.fetchEvents(mailgunOptions, batchHandler, options);
}
}
module.exports = EmailAnalyticsProviderMailgun;