diff --git a/ghost/core/core/server/services/email-analytics/jobs/index.js b/ghost/core/core/server/services/email-analytics/jobs/index.js index d067667703..d17e4fb654 100644 --- a/ghost/core/core/server/services/email-analytics/jobs/index.js +++ b/ghost/core/core/server/services/email-analytics/jobs/index.js @@ -7,7 +7,7 @@ const jobsService = require('../../jobs'); let hasScheduled = false; module.exports = { - async scheduleRecurringJobs() { + async scheduleRecurringJobs(skipEmailCheck = false) { if ( !hasScheduled && config.get('emailAnalytics') && @@ -17,10 +17,10 @@ module.exports = { // Don't register email analytics job if we have no emails, // processor usage from many sites spinning up threads can be high. // Mega service will re-run this scheduling task when an email is sent - const emailCount = await models.Email + const emailCount = skipEmailCheck ? 1 : (await models.Email .where('created_at', '>', moment.utc().subtract(30, 'days').toDate()) .where('status', '<>', 'failed') - .count(); + .count()); if (emailCount > 0) { // use a random seconds value to avoid spikes to external APIs on the minute diff --git a/ghost/core/core/server/services/email-service/wrapper.js b/ghost/core/core/server/services/email-service/wrapper.js index 9bf3efe442..4c0ae00de6 100644 --- a/ghost/core/core/server/services/email-service/wrapper.js +++ b/ghost/core/core/server/services/email-service/wrapper.js @@ -35,6 +35,7 @@ class EmailServiceWrapper { const linkTracking = require('../link-tracking'); const audienceFeedback = require('../audience-feedback'); const storageUtils = require('../../adapters/storage/utils'); + const emailAnalyticsJobs = require('../email-analytics/jobs'); // capture errors from mailgun client and log them in sentry const errorHandler = (error) => { @@ -103,7 +104,8 @@ class EmailServiceWrapper { emailSegmenter, limitService, membersRepository, - verificationTrigger: membersService.verificationTrigger + verificationTrigger: membersService.verificationTrigger, + emailAnalyticsJobs }); this.controller = new EmailController(this.service, { diff --git a/ghost/email-service/lib/email-service.js b/ghost/email-service/lib/email-service.js index cb80a142fd..0b37ff2ca9 100644 --- a/ghost/email-service/lib/email-service.js +++ b/ghost/email-service/lib/email-service.js @@ -13,6 +13,7 @@ const tpl = require('@tryghost/tpl'); const EmailRenderer = require('./email-renderer'); const EmailSegmenter = require('./email-segmenter'); const SendingService = require('./sending-service'); +const logging = require('@tryghost/logging'); const messages = { archivedNewsletterError: 'Cannot send email to archived newsletters', @@ -30,6 +31,7 @@ class EmailService { #limitService; #membersRepository; #verificationTrigger; + #emailAnalyticsJobs; /** * @@ -44,6 +46,7 @@ class EmailService { * @param {LimitService} dependencies.limitService * @param {object} dependencies.membersRepository * @param {VerificationTrigger} dependencies.verificationTrigger + * @param {object} dependencies.emailAnalyticsJobs */ constructor({ batchSendingService, @@ -54,7 +57,8 @@ class EmailService { emailSegmenter, limitService, membersRepository, - verificationTrigger + verificationTrigger, + emailAnalyticsJobs }) { this.#batchSendingService = batchSendingService; this.#models = models; @@ -65,6 +69,7 @@ class EmailService { this.#membersRepository = membersRepository; this.#sendingService = sendingService; this.#verificationTrigger = verificationTrigger; + this.#emailAnalyticsJobs = emailAnalyticsJobs; } /** @@ -141,6 +146,13 @@ class EmailService { }, {patch: true}); } + // make sure recurring background analytics jobs are running once we have emails + try { + await this.#emailAnalyticsJobs.scheduleRecurringJobs(true); + } catch (e) { + logging.error(e); + } + return email; } async retryEmail(email) { diff --git a/ghost/email-service/test/email-service.test.js b/ghost/email-service/test/email-service.test.js index 6c5eaa197b..fabd630284 100644 --- a/ghost/email-service/test/email-service.test.js +++ b/ghost/email-service/test/email-service.test.js @@ -10,6 +10,7 @@ describe('Email Service', function () { let membersRepository; let emailRenderer; let sendingService; + let scheduleRecurringJobs; beforeEach(function () { memberCount = 123; @@ -19,6 +20,7 @@ describe('Email Service', function () { }; verificicationRequired = false; scheduleEmail = sinon.stub().returns(); + scheduleRecurringJobs = sinon.stub().resolves(); settings = {}; settingsCache = { get(key) { @@ -85,7 +87,10 @@ describe('Email Service', function () { settingsCache, emailRenderer, membersRepository, - sendingService + sendingService, + emailAnalyticsJobs: { + scheduleRecurringJobs + } }); }); @@ -155,6 +160,22 @@ describe('Email Service', function () { assert.strictEqual(email.get('status'), 'pending'); assert.strictEqual(email.get('source'), post.get('mobiledoc')); assert.strictEqual(email.get('source_type'), 'mobiledoc'); + sinon.assert.calledOnce(scheduleRecurringJobs); + }); + + it('Ignores analytics job scheduling errors', async function () { + const post = createModel({ + id: '123', + newsletter: createModel({ + status: 'active', + feedback_enabled: true + }), + mobiledoc: 'Mobiledoc' + }); + + scheduleRecurringJobs.rejects(new Error('Test error')); + await service.createEmail(post); + sinon.assert.calledOnce(scheduleRecurringJobs); }); it('Creates and schedules an email with lexical', async function () {