diff --git a/ghost/core/core/shared/config/defaults.json b/ghost/core/core/shared/config/defaults.json index be39df57cb..191ddc4f78 100644 --- a/ghost/core/core/shared/config/defaults.json +++ b/ghost/core/core/shared/config/defaults.json @@ -228,5 +228,8 @@ "members": [0, 100, 1000, 10000, 25000, 50000, 100000, 250000, 500000, 1000000], "minDaysSinceImported": 7, "minDaysSinceLastEmail": 14 - } + }, + "bulkEmail": { + "batchSize": 1000 + } } diff --git a/ghost/core/test/integration/services/email-service/batch-sending.test.js b/ghost/core/test/integration/services/email-service/batch-sending.test.js index c8384ebdb9..47a404bad5 100644 --- a/ghost/core/test/integration/services/email-service/batch-sending.test.js +++ b/ghost/core/test/integration/services/email-service/batch-sending.test.js @@ -6,7 +6,6 @@ const sinon = require('sinon'); const assert = require('assert/strict'); const jobManager = require('../../../../core/server/services/jobs/job-service'); const _ = require('lodash'); -const {MailgunEmailProvider} = require('@tryghost/email-service'); const escapeRegExp = require('lodash/escapeRegExp'); const configUtils = require('../../../utils/configUtils'); const {settingsCache} = require('../../../../core/server/services/settings-helpers'); @@ -259,7 +258,7 @@ describe('Batch sending tests', function () { let ghostServer; beforeEach(function () { - MailgunEmailProvider.BATCH_SIZE = 100; + configUtils.set('bulkEmail:batchSize', 100); stubbedSend = sinon.fake.resolves({ id: 'stubbed-email-id' }); @@ -548,7 +547,7 @@ describe('Batch sending tests', function () { }); it('Splits up in batches according to email provider batch size', async function () { - MailgunEmailProvider.BATCH_SIZE = 1; + configUtils.set('bulkEmail:batchSize', 1); await testEmailBatches({ mobiledoc: mobileDocExample }, null, [ @@ -561,7 +560,7 @@ describe('Batch sending tests', function () { }); it('Splits up in batches according to email provider batch size with paid and free segments', async function () { - MailgunEmailProvider.BATCH_SIZE = 1; + configUtils.set('bulkEmail:batchSize', 1); await testEmailBatches({ mobiledoc: mobileDocWithPaidMemberOnly }, null, [ @@ -577,7 +576,7 @@ describe('Batch sending tests', function () { }); it('One failed batch marks the email as failed and allows for a retry', async function () { - MailgunEmailProvider.BATCH_SIZE = 1; + configUtils.set('bulkEmail:batchSize', 1); let counter = 0; stubbedSend = async function () { counter += 1; diff --git a/ghost/email-service/lib/MailgunEmailProvider.js b/ghost/email-service/lib/MailgunEmailProvider.js index 369062b205..622a712b4f 100644 --- a/ghost/email-service/lib/MailgunEmailProvider.js +++ b/ghost/email-service/lib/MailgunEmailProvider.js @@ -30,8 +30,6 @@ class MailgunEmailProvider { #mailgunClient; #errorHandler; - static BATCH_SIZE = 1000; - /** * @param {object} dependencies * @param {import('@tryghost/mailgun-client/lib/MailgunClient')} dependencies.mailgunClient - mailgun client to send emails @@ -172,7 +170,7 @@ class MailgunEmailProvider { } getMaximumRecipients() { - return MailgunEmailProvider.BATCH_SIZE; + return this.#mailgunClient.getBatchSize(); } } diff --git a/ghost/email-service/test/mailgun-email-provider.test.js b/ghost/email-service/test/mailgun-email-provider.test.js index 692e7e9853..0001ea7a84 100644 --- a/ghost/email-service/test/mailgun-email-provider.test.js +++ b/ghost/email-service/test/mailgun-email-provider.test.js @@ -225,8 +225,20 @@ describe('Mailgun Email Provider', function () { }); describe('getMaximumRecipients', function () { + let mailgunClient; + let getBatchSizeStub; + it('returns 1000', function () { - const provider = new MailgunEmailProvider({}); + getBatchSizeStub = sinon.stub().returns(1000); + + mailgunClient = { + getBatchSize: getBatchSizeStub + }; + + const provider = new MailgunEmailProvider({ + mailgunClient, + errorHandler: () => {} + }); assert.equal(provider.getMaximumRecipients(), 1000); }); }); diff --git a/ghost/mailgun-client/lib/MailgunClient.js b/ghost/mailgun-client/lib/MailgunClient.js index 5d0a052e30..bca196d876 100644 --- a/ghost/mailgun-client/lib/MailgunClient.js +++ b/ghost/mailgun-client/lib/MailgunClient.js @@ -8,7 +8,7 @@ module.exports = class MailgunClient { #config; #settings; - static BATCH_SIZE = 1000; + static DEFAULT_BATCH_SIZE = 1000; constructor({config, settings}) { this.#config = config; @@ -38,9 +38,10 @@ module.exports = class MailgunClient { return null; } - if (Object.keys(recipientData).length > MailgunClient.BATCH_SIZE) { + const batchSize = this.getBatchSize(); + if (Object.keys(recipientData).length > batchSize) { throw new errors.IncorrectUsageError({ - message: `Mailgun only supports sending to ${MailgunClient.BATCH_SIZE} recipients at a time` + message: `Mailgun only supports sending to ${batchSize} recipients at a time` }); } @@ -305,4 +306,13 @@ module.exports = class MailgunClient { const instance = this.getInstance(); return !!instance; } + + /** + * Returns configured batch size + * + * @returns {number} + */ + getBatchSize() { + return this.#config.get('bulkEmail')?.batchSize ?? this.DEFAULT_BATCH_SIZE; + } }; diff --git a/ghost/mailgun-client/test/mailgun-client.test.js b/ghost/mailgun-client/test/mailgun-client.test.js index 387fe6c9cc..2dd4546c27 100644 --- a/ghost/mailgun-client/test/mailgun-client.test.js +++ b/ghost/mailgun-client/test/mailgun-client.test.js @@ -43,8 +43,19 @@ describe('MailgunClient', function () { sinon.restore(); }); - it('exports a number for BATCH_SIZE', function () { - assert(typeof MailgunClient.BATCH_SIZE === 'number'); + it('exports a number for configurable batch size', 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 + }); + + const mailgunClient = new MailgunClient({config, settings}); + assert(typeof mailgunClient.getBatchSize() === 'number'); }); it('can connect via config', function () { @@ -54,7 +65,8 @@ describe('MailgunClient', function () { apiKey: 'apiKey', domain: 'domain.com', baseUrl: 'https://api.mailgun.net/v3' - } + }, + batchSize: 1000 }); const mailgunClient = new MailgunClient({config, settings}); @@ -116,7 +128,8 @@ describe('MailgunClient', function () { apiKey: 'apiKey', domain: 'configdomain.com', baseUrl: 'https://api.mailgun.net' - } + }, + batchSize: 1000 }); const settingsStub = sinon.stub(settings, 'get'); @@ -170,7 +183,8 @@ describe('MailgunClient', function () { apiKey: 'apiKey', domain: 'domain.com', baseUrl: 'https://api.mailgun.net/v3' - } + }, + batchSize: 1000 }); const firstPageMock = nock('https://api.mailgun.net') @@ -214,7 +228,8 @@ describe('MailgunClient', function () { apiKey: 'apiKey', domain: 'domain.com', baseUrl: 'https://api.mailgun.net/v3' - } + }, + batchSize: 1000 }); const firstPageMock = nock('https://api.mailgun.net') @@ -259,7 +274,8 @@ describe('MailgunClient', function () { apiKey: 'apiKey', domain: 'domain.com', baseUrl: 'https://api.mailgun.net/v3' - } + }, + batchSize: 1000 }); const firstPageMock = nock('https://api.mailgun.net') @@ -304,7 +320,8 @@ describe('MailgunClient', function () { apiKey: 'apiKey', domain: 'domain.com', baseUrl: 'https://api.mailgun.net/v3' - } + }, + batchSize: 1000 }); const firstPageMock = nock('https://api.mailgun.net') @@ -349,7 +366,8 @@ describe('MailgunClient', function () { apiKey: 'apiKey', domain: 'domain.com', baseUrl: 'https://api.eu.mailgun.net/v3' - } + }, + batchSize: 1000 }); const firstPageMock = nock('https://api.eu.mailgun.net')