Extracted batchDelay into configuration

This commit is contained in:
Chris Raible 2024-08-08 18:58:13 -07:00
parent f15d4c122f
commit 7393f5857f
8 changed files with 152 additions and 9 deletions

View File

@ -357,6 +357,8 @@ class BatchSendingService {
async sendBatches({email, batches, post, newsletter}) {
logging.info(`Sending ${batches.length} batches for email ${email.id}`);
const BATCH_DELAY = this.#sendingService.getBatchDelay(); // delay between batches in milliseconds
// Reuse same HTML body if we send an email to the same segment
const emailBodyCache = new EmailBodyCache();
@ -370,9 +372,12 @@ class BatchSendingService {
runNext = async () => {
const batch = queue.shift();
if (batch) {
const index = batches.indexOf(batch);
const deliveryDelay = Math.abs(index * 5000);
const deliveryTime = new Date(startTime.getTime() + deliveryDelay);
let deliveryTime = undefined;
if (BATCH_DELAY > 0) {
const index = batches.indexOf(batch);
const deliveryDelay = Math.abs(index * BATCH_DELAY);
deliveryTime = new Date(startTime.getTime() + deliveryDelay);
}
if (await this.sendBatch({email, batch, post, newsletter, emailBodyCache, deliveryTime})) {
succeededCount += 1;
}
@ -442,11 +447,11 @@ class BatchSendingService {
post,
newsletter,
segment: batch.get('member_segment'),
members,
deliveryTime
members
}, {
openTrackingEnabled: !!email.get('track_opens'),
clickTrackingEnabled: !!email.get('track_clicks'),
deliveryTime,
emailBodyCache
});
}, {...this.#MAILGUN_API_RETRY_CONFIG, description: `Sending email batch ${originalBatch.id}`});

View File

@ -177,6 +177,15 @@ class MailgunEmailProvider {
getMaximumRecipients() {
return this.#mailgunClient.getBatchSize();
}
/**
* Returns the configured delay between batches in milliseconds
*
* @returns {number}
*/
getBatchDelay() {
return this.#mailgunClient.getBatchDelay();
}
}
module.exports = MailgunEmailProvider;

View File

@ -76,6 +76,15 @@ class SendingService {
return this.#emailProvider.getMaximumRecipients();
}
/**
* Returns the configured delay between batches in milliseconds
*
* @returns {number}
*/
getBatchDelay() {
return this.#emailProvider.getBatchDelay();
}
/**
* Send a given post, rendered for a given newsletter and segment to the members provided in the list
* @param {object} data

View File

@ -681,7 +681,13 @@ describe('Batch Sending Service', function () {
describe('sendBatches', function () {
it('Works for a single batch', async function () {
const service = new BatchSendingService({});
const service = new BatchSendingService({
sendingService: {
getBatchDelay() {
return 0;
}
}
});
const sendBatch = sinon.stub(service, 'sendBatch').callsFake(() => {
return Promise.resolve(true);
});
@ -700,7 +706,13 @@ describe('Batch Sending Service', function () {
});
it('Works for more than 2 batches', async function () {
const service = new BatchSendingService({});
const service = new BatchSendingService({
sendingService: {
getBatchDelay() {
return 0;
}
}
});
let runningCount = 0;
let maxRunningCount = 0;
const sendBatch = sinon.stub(service, 'sendBatch').callsFake(async () => {
@ -723,8 +735,51 @@ describe('Batch Sending Service', function () {
assert.equal(maxRunningCount, 2);
});
it('Works with a batchDelay > 0', async function () {
const service = new BatchSendingService({
sendingService: {
getBatchDelay() {
return 1000;
}
}
});
let runningCount = 0;
let maxRunningCount = 0;
const sendBatch = sinon.stub(service, 'sendBatch').callsFake(async () => {
runningCount += 1;
maxRunningCount = Math.max(maxRunningCount, runningCount);
await sleep(5);
runningCount -= 1;
return Promise.resolve(true);
});
const batches = new Array(101).fill(0).map(() => createModel({}));
await service.sendBatches({
email: createModel({}),
batches,
post: createModel({}),
newsletter: createModel({})
});
sinon.assert.callCount(sendBatch, 101);
const sendBatches = sendBatch.getCalls().map(call => call.args[0].batch);
const deliveryTimes = sendBatch.getCalls().map(call => call.args[0].deliveryTime);
deliveryTimes.forEach((time, i) => {
assert(time instanceof Date);
if (i > 0) {
assert(time - deliveryTimes[i - 1] >= 1000);
}
});
assert.deepEqual(sendBatches, batches);
assert.equal(maxRunningCount, 2);
});
it('Throws error if all batches fail', async function () {
const service = new BatchSendingService({});
const service = new BatchSendingService({
sendingService: {
getBatchDelay() {
return 0;
}
}
});
let runningCount = 0;
let maxRunningCount = 0;
const sendBatch = sinon.stub(service, 'sendBatch').callsFake(async () => {
@ -748,7 +803,13 @@ describe('Batch Sending Service', function () {
});
it('Throws error if a single batch fails', async function () {
const service = new BatchSendingService({});
const service = new BatchSendingService({
sendingService: {
getBatchDelay() {
return 0;
}
}
});
let runningCount = 0;
let maxRunningCount = 0;
let callCount = 0;

View File

@ -246,4 +246,23 @@ describe('Mailgun Email Provider', function () {
assert.equal(provider.getMaximumRecipients(), 1000);
});
});
describe('getBatchDelay', function () {
let mailgunClient;
let getBatchDelayStub;
it('returns 1000', function () {
getBatchDelayStub = sinon.stub().returns(0);
mailgunClient = {
getBatchDelay: getBatchDelayStub
};
const provider = new MailgunEmailProvider({
mailgunClient,
errorHandler: () => {}
});
assert.equal(provider.getBatchDelay(), 0);
});
});
});

View File

@ -391,4 +391,17 @@ describe('Sending service', function () {
sinon.assert.calledOnce(emailProvider.getMaximumRecipients);
});
});
describe('getBatchDelay', function () {
it('returns the batch delay of the email provider', function () {
const emailProvider = {
getBatchDelay: sinon.stub().returns(0)
};
const sendingService = new SendingService({
emailProvider
});
assert.equal(sendingService.getBatchDelay(), 0);
sinon.assert.calledOnce(emailProvider.getBatchDelay);
});
});
});

View File

@ -327,4 +327,15 @@ module.exports = class MailgunClient {
getBatchSize() {
return this.#config.get('bulkEmail')?.batchSize ?? this.DEFAULT_BATCH_SIZE;
}
/**
* Returns the configurated delay between batches in milliseconds
*
* Defaults to 0 (no delay) if not set
*
* @returns {number}
*/
getBatchDelay() {
return this.#config.get('bulkEmail')?.batchDelay ?? 0;
}
};

View File

@ -58,6 +58,22 @@ describe('MailgunClient', function () {
assert(typeof mailgunClient.getBatchSize() === 'number');
});
it('exports a number for configurable batch delay', function () {
const configStub = sinon.stub(config, 'get');
configStub.withArgs('bulkEmail').returns({
mailgun: {
apiKey: 'apiKey',
domain: 'domain.com',
baseUrl: 'https://api.mailgun.net/v3'
},
batchSize: 1000,
batchDelay: 1000
});
const mailgunClient = new MailgunClient({config, settings});
assert(typeof mailgunClient.getBatchDelay() === 'number');
});
it('can connect via config', function () {
const configStub = sinon.stub(config, 'get');
configStub.withArgs('bulkEmail').returns({