136 lines
4.7 KiB
JavaScript
136 lines
4.7 KiB
JavaScript
|
const _ = require('lodash');
|
||
|
const mailgunJs = require('mailgun-js');
|
||
|
const moment = require('moment');
|
||
|
const EventProcessingResult = require('../lib/event-processing-result');
|
||
|
|
||
|
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 EmailAnalyticsMailgunProvider {
|
||
|
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.items.map(this.normalizeEvent);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
normalizeEvent(event) {
|
||
|
// TODO: clean up the <> surrounding email_batches.provider_id values
|
||
|
let providerId = event.message && event.message.headers && event.message.headers['message-id'];
|
||
|
if (providerId) {
|
||
|
providerId = `<${providerId}>`;
|
||
|
}
|
||
|
|
||
|
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 = EmailAnalyticsMailgunProvider;
|