ghost 6.9.0 → 6.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/components/tryghost-i18n-6.9.3.tgz +0 -0
  2. package/core/built/admin/assets/activitypub/activitypub.js +2 -2
  3. package/core/built/admin/assets/activitypub/{index-C19nEXqT.mjs → index-D-0TWnTq.mjs} +2447 -2443
  4. package/core/built/admin/assets/activitypub/{index-B29oZuTp.mjs → index-Dz4ykJpN.mjs} +2 -2
  5. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-BNKxdfRt.mjs → CodeEditorView-C4K2SFCa.mjs} +2 -2
  6. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  7. package/core/built/admin/assets/admin-x-settings/{index-B-_a183c.mjs → index-D1JNSNMf.mjs} +2 -2
  8. package/core/built/admin/assets/admin-x-settings/{index-CjRGpMVv.mjs → index-DLS4G2Af.mjs} +2 -2
  9. package/core/built/admin/assets/admin-x-settings/{index-Q0XmL0KU.mjs → index-jt7mifza.mjs} +23310 -22917
  10. package/core/built/admin/assets/admin-x-settings/{modals-omgXN6i-.mjs → modals-Bkrvssih.mjs} +3336 -3339
  11. package/core/built/admin/assets/{chunk.524.774a2df444e2ffde4942.js → chunk.524.6d040cb21c767f0236ae.js} +7 -7
  12. package/core/built/admin/assets/{chunk.582.ca4f05f3c39fda05b54c.js → chunk.582.e35aed09ef750c552601.js} +9 -9
  13. package/core/built/admin/assets/ghost-192beb3c8f2f6e58b70d3aade481ae15.css +1 -0
  14. package/core/built/admin/assets/{ghost-94d0fbb20e8e880fa9ba144cf26ab050.js → ghost-5fd8d4d5e1ffe4d8405d30f3efba12b7.js} +105 -89
  15. package/core/built/admin/assets/ghost-dark-e50f4c063ecc977f023e78da2dd67b42.css +1 -0
  16. package/core/built/admin/assets/posts/posts.js +6745 -6741
  17. package/core/built/admin/assets/stats/stats.js +16015 -16011
  18. package/core/built/admin/index.html +4 -4
  19. package/core/server/data/tinybird/datasources/_mv_hits.datasource +4 -1
  20. package/core/server/data/tinybird/endpoints/README.md +109 -0
  21. package/core/server/data/tinybird/endpoints/api_kpis.pipe +2 -23
  22. package/core/server/data/tinybird/endpoints/api_monitoring_ingestion.pipe +14 -13
  23. package/core/server/data/tinybird/endpoints/api_monitoring_ingestion_aggregated.pipe +13 -12
  24. package/core/server/data/tinybird/endpoints/api_top_locations.pipe +0 -19
  25. package/core/server/data/tinybird/endpoints/api_top_pages.pipe +0 -23
  26. package/core/server/data/tinybird/endpoints/api_top_sources.pipe +0 -11
  27. package/core/server/data/tinybird/endpoints/api_top_utm_campaigns.pipe +0 -6
  28. package/core/server/data/tinybird/endpoints/api_top_utm_contents.pipe +0 -6
  29. package/core/server/data/tinybird/endpoints/api_top_utm_mediums.pipe +0 -6
  30. package/core/server/data/tinybird/endpoints/api_top_utm_sources.pipe +0 -6
  31. package/core/server/data/tinybird/endpoints/api_top_utm_terms.pipe +0 -6
  32. package/core/server/data/tinybird/pipes/filtered_sessions.pipe +28 -8
  33. package/core/server/data/tinybird/pipes/mv_hits.pipe +23 -11
  34. package/core/server/data/tinybird/tests/api_top_pages.yaml +5 -2
  35. package/core/server/services/email-analytics/EmailAnalyticsService.js +53 -6
  36. package/core/server/services/email-service/DomainWarmingService.js +47 -9
  37. package/core/server/services/email-service/DomainWarmingService.ts +62 -11
  38. package/core/server/services/email-service/EmailServiceWrapper.js +2 -1
  39. package/core/server/services/member-welcome-emails/jobs/index.js +13 -9
  40. package/package.json +3 -3
  41. package/tsconfig.tsbuildinfo +1 -1
  42. package/yarn.lock +5 -50
  43. package/components/tryghost-i18n-6.9.0.tgz +0 -0
  44. package/core/built/admin/assets/ghost-dark-6c9cfa9c364e28c57e5983f68ec6f2fc.css +0 -1
  45. package/core/built/admin/assets/ghost-f724c1d53f5402f78a2d8cf8beb7c716.css +0 -1
  46. package/core/built/admin/assets/img/google-docs-1e42cc272fc088da49e4b0ddfb01b006.svg +0 -6
  47. package/core/built/admin/assets/img/mailchimp-f22b1e130aac764965b9306d7265a6b2.svg +0 -8
  48. package/core/built/admin/assets/img/patreon-b19a5e6418a72977a16b30039d374d04.svg +0 -7
  49. package/core/built/admin/assets/img/paypal-38e9448ce7549ea4caf8e7753ae661d6.svg +0 -8
  50. package/core/built/admin/assets/img/slackicon-406aadea8994ca2ddee9c1d7157208db.png +0 -0
  51. package/core/built/admin/assets/img/themes/Alto-0dfe76694ed222d6d96fc9c8db979a38.png +0 -0
  52. package/core/built/admin/assets/img/themes/Bulletin-d66dec818ad0ba2965dd7eb3130c621c.png +0 -0
  53. package/core/built/admin/assets/img/themes/Casper-9a0ce71df3a1c589c1414ad2aa5b8aeb.png +0 -0
  54. package/core/built/admin/assets/img/themes/Dawn-302fbfdbc352098a256137159bd83dd8.png +0 -0
  55. package/core/built/admin/assets/img/themes/Digest-698e78d8e8481daff8ae4a8647528dc9.png +0 -0
  56. package/core/built/admin/assets/img/themes/Dope-d099dfca697adae16baa76f89520c5b3.png +0 -0
  57. package/core/built/admin/assets/img/themes/Ease-7075f809892f10c58892e15a57a4aae4.png +0 -0
  58. package/core/built/admin/assets/img/themes/Edge-1e5e0eec6941d7bdca02cebb66187357.png +0 -0
  59. package/core/built/admin/assets/img/themes/Edition-10111a2b8458168dcff81b7fb151be70.png +0 -0
  60. package/core/built/admin/assets/img/themes/Episode-e4c86d1f75ef1d8a77791d7fd519fdd4.png +0 -0
  61. package/core/built/admin/assets/img/themes/Headline-f70eaf49b9fcae1ddfe3d4496d8be54d.png +0 -0
  62. package/core/built/admin/assets/img/themes/Journal-07d35b2311501d2738bad1907ba2f7e1.png +0 -0
  63. package/core/built/admin/assets/img/themes/London-4e042390da16fecef947f3a7001d03db.png +0 -0
  64. package/core/built/admin/assets/img/themes/Ruby-b896885448e0f28ca62c6dcd56c732aa.png +0 -0
  65. package/core/built/admin/assets/img/themes/Solo-0292eb9ae0ca7b578cff50824d40cc86.png +0 -0
  66. package/core/built/admin/assets/img/themes/Source-Magazine-176385eb99561485ce6385598f7599b0.png +0 -0
  67. package/core/built/admin/assets/img/themes/Source-Newsletter-bb1a8611a326edb78f9ecb8bc1838c81.png +0 -0
  68. package/core/built/admin/assets/img/themes/Source-f29c6d6abe774ea7d5940a7431069fce.png +0 -0
  69. package/core/built/admin/assets/img/themes/Taste-a24d2a786900d9caff7e773ca0040991.png +0 -0
  70. package/core/built/admin/assets/img/themes/Wave-b98fadfd3ed16e8b2e383dd8e8e5dca2.png +0 -0
  71. package/core/built/admin/assets/img/typeform-9f23f8712d776a7515594676285266f5.svg +0 -7
  72. package/core/built/admin/assets/img/zero-bounce-ee799eddb1f88e33ab8c462858cbfed9.png +0 -0
@@ -330,22 +330,56 @@ module.exports = class EmailAnalyticsService {
330
330
 
331
331
  // We keep the processing result here, so we also have a result in case of failures
332
332
  let processingResult = new EventProcessingResult();
333
+ // Track cumulative event counts separately since processingResult gets reset during intermediate aggregations
334
+ const cumulativeResult = new EventProcessingResult();
335
+ // Track all unique emailIds and memberIds that need aggregation
336
+ const allEmailIds = new Set();
337
+ const allMemberIds = new Set();
333
338
  let error = null;
334
339
 
335
340
  /**
336
341
  * Process a batch of events
337
342
  * @param {Array<Object>} events - Array of event objects to process
338
- * @param {EventProcessingResult} processingResult - Object to store the processing results
339
- * @param {FetchData} fetchData - Object containing fetch operation data
340
343
  * @returns {Promise<void>}
341
344
  */
342
345
  const processBatch = async (events) => {
343
346
  // Even if the fetching is interrupted because of an error, we still store the last event timestamp
344
347
  const processingStart = Date.now();
348
+ // Capture the state before processing to calculate delta
349
+ const beforeCounts = {
350
+ opened: processingResult.opened,
351
+ delivered: processingResult.delivered,
352
+ temporaryFailed: processingResult.temporaryFailed,
353
+ permanentFailed: processingResult.permanentFailed,
354
+ unsubscribed: processingResult.unsubscribed,
355
+ complained: processingResult.complained,
356
+ unhandled: processingResult.unhandled,
357
+ unprocessable: processingResult.unprocessable
358
+ };
359
+ const beforeEmailIds = new Set(processingResult.emailIds);
360
+ const beforeMemberIds = new Set(processingResult.memberIds);
361
+
345
362
  await this.processEventBatch(events, processingResult, fetchData);
346
363
  processingTimeMs += (Date.now() - processingStart);
347
364
  eventCount += events.length;
348
365
 
366
+ // Calculate delta (only new counts from this batch) and accumulate for final reporting
367
+ const batchDelta = new EventProcessingResult({
368
+ opened: processingResult.opened - beforeCounts.opened,
369
+ delivered: processingResult.delivered - beforeCounts.delivered,
370
+ temporaryFailed: processingResult.temporaryFailed - beforeCounts.temporaryFailed,
371
+ permanentFailed: processingResult.permanentFailed - beforeCounts.permanentFailed,
372
+ unsubscribed: processingResult.unsubscribed - beforeCounts.unsubscribed,
373
+ complained: processingResult.complained - beforeCounts.complained,
374
+ unhandled: processingResult.unhandled - beforeCounts.unhandled,
375
+ unprocessable: processingResult.unprocessable - beforeCounts.unprocessable,
376
+ emailIds: processingResult.emailIds.filter(id => !beforeEmailIds.has(id)),
377
+ memberIds: processingResult.memberIds.filter(id => !beforeMemberIds.has(id))
378
+ });
379
+ cumulativeResult.merge(batchDelta);
380
+ batchDelta.emailIds.forEach(id => allEmailIds.add(id));
381
+ batchDelta.memberIds.forEach(id => allMemberIds.add(id));
382
+
349
383
  // Every 5 minutes or 5000 members we do an aggregation and clear the processingResult
350
384
  // Otherwise we need to loop a lot of members afterwards, and this takes too long without updating the stat counts in between
351
385
  if ((Date.now() - lastAggregation > 5 * 60 * 1000 || processingResult.memberIds.length > 5000) && eventCount > 0) {
@@ -356,6 +390,9 @@ module.exports = class EmailAnalyticsService {
356
390
  await this.aggregateStats(processingResult, includeOpenedEvents);
357
391
  aggregationTimeMs += (Date.now() - aggregationStart);
358
392
  lastAggregation = Date.now();
393
+ // Remove aggregated emailIds and memberIds from tracking sets to avoid re-aggregating at the end
394
+ processingResult.emailIds.forEach(id => allEmailIds.delete(id));
395
+ processingResult.memberIds.forEach(id => allMemberIds.delete(id));
359
396
  processingResult = new EventProcessingResult();
360
397
  } catch (err) {
361
398
  logging.error('[EmailAnalytics] Error while aggregating stats');
@@ -386,10 +423,20 @@ module.exports = class EmailAnalyticsService {
386
423
  }
387
424
  }
388
425
 
389
- if (processingResult.memberIds.length > 0 || processingResult.emailIds.length > 0) {
426
+ // Final aggregation: aggregate any remaining events and ensure all emailIds are aggregated
427
+ // We need to aggregate all unique emailIds to ensure the emails table is updated
428
+ const finalEmailIds = Array.from(new Set([...processingResult.emailIds, ...allEmailIds]));
429
+ const finalMemberIds = Array.from(new Set([...processingResult.memberIds, ...allMemberIds]));
430
+
431
+ if (finalMemberIds.length > 0 || finalEmailIds.length > 0) {
390
432
  try {
391
433
  const aggregationStart = Date.now();
392
- await this.aggregateStats(processingResult, includeOpenedEvents);
434
+ // Create a result object with all emailIds and memberIds for final aggregation
435
+ const finalAggregationResult = {
436
+ emailIds: finalEmailIds,
437
+ memberIds: finalMemberIds
438
+ };
439
+ await this.aggregateStats(finalAggregationResult, includeOpenedEvents);
393
440
  aggregationTimeMs += (Date.now() - aggregationStart);
394
441
  } catch (err) {
395
442
  logging.error('[EmailAnalytics] Error while aggregating stats');
@@ -404,7 +451,7 @@ module.exports = class EmailAnalyticsService {
404
451
  // Small trick: if reached the end of new events, we are going to keep
405
452
  // fetching the same events because 'begin' won't change
406
453
  // So if we didn't have errors while fetching, and total events < maxEvents, increase lastEventTimestamp with one second
407
- if (!error && eventCount > 0 && eventCount < maxEvents && fetchData.lastEventTimestamp && fetchData.lastEventTimestamp.getTime() < Date.now() - 2000) {
454
+ if (!error && eventCount > 0 && fetchData.lastEventTimestamp && fetchData.lastEventTimestamp.getTime() < Date.now() - 2000) {
408
455
  // set the data on the db so we can store it for fetching after reboot
409
456
  await this.queries.setJobTimestamp(fetchData.jobName, 'finished', new Date(fetchData.lastEventTimestamp.getTime()));
410
457
  // increment and store in local memory
@@ -425,7 +472,7 @@ module.exports = class EmailAnalyticsService {
425
472
  apiPollingTimeMs,
426
473
  processingTimeMs,
427
474
  aggregationTimeMs,
428
- result: processingResult
475
+ result: cumulativeResult
429
476
  };
430
477
  }
431
478
 
@@ -1,32 +1,62 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DomainWarmingService = void 0;
4
+ /**
5
+ * Configuration for domain warming email volume scaling.
6
+ *
7
+ * | Volume Range | Multiplier |
8
+ * |--------------|--------------------------------------------------|
9
+ * | ≤100 (base) | 200 messages |
10
+ * | 101 – 1k | 1.25× (conservative early ramp) |
11
+ * | 1k – 5k | 1.5× (moderate increase) |
12
+ * | 5k – 100k | 1.75× (faster ramp after proving deliverability) |
13
+ * | 100k – 400k | 2× |
14
+ * | 400k+ | min(1.2×, +75k) cap |
15
+ */
4
16
  const WARMUP_SCALING_TABLE = {
5
17
  base: {
6
18
  limit: 100,
7
19
  value: 200
8
20
  },
9
21
  thresholds: [{
22
+ limit: 1_000,
23
+ scale: 1.25
24
+ }, {
25
+ limit: 5_000,
26
+ scale: 1.5
27
+ }, {
10
28
  limit: 100_000,
11
- scale: 2
29
+ scale: 1.75
12
30
  }, {
13
31
  limit: 400_000,
14
- scale: 1.5
32
+ scale: 2
15
33
  }],
16
- defaultScale: 1.25
34
+ highVolume: {
35
+ threshold: 400_000,
36
+ maxScale: 1.2,
37
+ maxAbsoluteIncrease: 75_000
38
+ }
17
39
  };
18
40
  class DomainWarmingService {
19
41
  #emailModel;
20
42
  #labs;
43
+ #config;
21
44
  constructor(dependencies) {
22
45
  this.#emailModel = dependencies.models.Email;
23
46
  this.#labs = dependencies.labs;
47
+ this.#config = dependencies.config;
24
48
  }
25
49
  /**
26
50
  * @returns Whether the domain warming feature is enabled
27
51
  */
28
52
  isEnabled() {
29
- return this.#labs.isSet('domainWarmup');
53
+ const hasLabsFlag = this.#labs.isSet('domainWarmup');
54
+ if (!hasLabsFlag) {
55
+ return false;
56
+ }
57
+ const fallbackDomain = this.#config.get('hostSettings:managedEmail:fallbackDomain');
58
+ const fallbackAddress = this.#config.get('hostSettings:managedEmail:fallbackAddress');
59
+ return Boolean(fallbackDomain && fallbackAddress);
30
60
  }
31
61
  /**
32
62
  * Get the maximum amount of emails that should be sent from the warming sending domain in today's newsletter
@@ -41,14 +71,15 @@ class DomainWarmingService {
41
71
  * @returns The highest number of messages sent from the CSD in a single email (excluding today)
42
72
  */
43
73
  async #getHighestCount() {
44
- const email = await this.#emailModel.findOne({
74
+ const result = await this.#emailModel.findPage({
45
75
  filter: `created_at:<${new Date().toISOString().split('T')[0]}`,
46
- order: 'csd_email_count DESC'
76
+ order: 'csd_email_count DESC',
77
+ limit: 1
47
78
  });
48
- if (!email) {
79
+ if (!result.data.length) {
49
80
  return 0;
50
81
  }
51
- const count = email.get('csd_email_count');
82
+ const count = result.data[0].get('csd_email_count');
52
83
  return count || 0;
53
84
  }
54
85
  /**
@@ -59,12 +90,19 @@ class DomainWarmingService {
59
90
  if (lastCount <= WARMUP_SCALING_TABLE.base.limit) {
60
91
  return WARMUP_SCALING_TABLE.base.value;
61
92
  }
93
+ // For high volume senders (400k+), cap the increase at 20% or 75k absolute
94
+ if (lastCount > WARMUP_SCALING_TABLE.highVolume.threshold) {
95
+ const scaledIncrease = Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale);
96
+ const absoluteIncrease = lastCount + WARMUP_SCALING_TABLE.highVolume.maxAbsoluteIncrease;
97
+ return Math.min(scaledIncrease, absoluteIncrease);
98
+ }
62
99
  for (const threshold of WARMUP_SCALING_TABLE.thresholds.sort((a, b) => a.limit - b.limit)) {
63
100
  if (lastCount <= threshold.limit) {
64
101
  return Math.ceil(lastCount * threshold.scale);
65
102
  }
66
103
  }
67
- return Math.ceil(lastCount * WARMUP_SCALING_TABLE.defaultScale);
104
+ // This should not be reached given the thresholds cover all cases up to highVolume.threshold
105
+ return Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale);
68
106
  }
69
107
  }
70
108
  exports.DomainWarmingService = DomainWarmingService;
@@ -2,8 +2,12 @@ type LabsService = {
2
2
  isSet: (flag: string) => boolean;
3
3
  };
4
4
 
5
+ type ConfigService = {
6
+ get: (key: string) => string | undefined;
7
+ }
8
+
5
9
  type EmailModel = {
6
- findOne: (options: {filter: string; order: string}) => Promise<EmailRecord | null>;
10
+ findPage: (options: {filter: string; order: string; limit: number}) => Promise<{data: EmailRecord[]}>;
7
11
  };
8
12
 
9
13
  type EmailRecord = {
@@ -20,41 +24,79 @@ type WarmupScalingTable = {
20
24
  limit: number;
21
25
  scale: number;
22
26
  }[];
23
- defaultScale: number;
27
+ highVolume: {
28
+ threshold: number;
29
+ maxScale: number;
30
+ maxAbsoluteIncrease: number;
31
+ };
24
32
  }
25
33
 
34
+ /**
35
+ * Configuration for domain warming email volume scaling.
36
+ *
37
+ * | Volume Range | Multiplier |
38
+ * |--------------|--------------------------------------------------|
39
+ * | ≤100 (base) | 200 messages |
40
+ * | 101 – 1k | 1.25× (conservative early ramp) |
41
+ * | 1k – 5k | 1.5× (moderate increase) |
42
+ * | 5k – 100k | 1.75× (faster ramp after proving deliverability) |
43
+ * | 100k – 400k | 2× |
44
+ * | 400k+ | min(1.2×, +75k) cap |
45
+ */
26
46
  const WARMUP_SCALING_TABLE: WarmupScalingTable = {
27
47
  base: {
28
48
  limit: 100,
29
49
  value: 200
30
50
  },
31
51
  thresholds: [{
52
+ limit: 1_000,
53
+ scale: 1.25
54
+ }, {
55
+ limit: 5_000,
56
+ scale: 1.5
57
+ }, {
32
58
  limit: 100_000,
33
- scale: 2
59
+ scale: 1.75
34
60
  }, {
35
61
  limit: 400_000,
36
- scale: 1.5
62
+ scale: 2
37
63
  }],
38
- defaultScale: 1.25
64
+ highVolume: {
65
+ threshold: 400_000,
66
+ maxScale: 1.2,
67
+ maxAbsoluteIncrease: 75_000
68
+ }
39
69
  };
40
70
 
41
71
  export class DomainWarmingService {
42
72
  #emailModel: EmailModel;
43
73
  #labs: LabsService;
74
+ #config: ConfigService;
44
75
 
45
76
  constructor(dependencies: {
46
77
  models: {Email: EmailModel};
47
78
  labs: LabsService;
79
+ config: ConfigService;
48
80
  }) {
49
81
  this.#emailModel = dependencies.models.Email;
50
82
  this.#labs = dependencies.labs;
83
+ this.#config = dependencies.config;
51
84
  }
52
85
 
53
86
  /**
54
87
  * @returns Whether the domain warming feature is enabled
55
88
  */
56
89
  isEnabled(): boolean {
57
- return this.#labs.isSet('domainWarmup');
90
+ const hasLabsFlag = this.#labs.isSet('domainWarmup');
91
+
92
+ if (!hasLabsFlag) {
93
+ return false;
94
+ }
95
+
96
+ const fallbackDomain = this.#config.get('hostSettings:managedEmail:fallbackDomain');
97
+ const fallbackAddress = this.#config.get('hostSettings:managedEmail:fallbackAddress');
98
+
99
+ return Boolean(fallbackDomain && fallbackAddress);
58
100
  }
59
101
 
60
102
  /**
@@ -72,16 +114,17 @@ export class DomainWarmingService {
72
114
  * @returns The highest number of messages sent from the CSD in a single email (excluding today)
73
115
  */
74
116
  async #getHighestCount(): Promise<number> {
75
- const email = await this.#emailModel.findOne({
117
+ const result = await this.#emailModel.findPage({
76
118
  filter: `created_at:<${new Date().toISOString().split('T')[0]}`,
77
- order: 'csd_email_count DESC'
119
+ order: 'csd_email_count DESC',
120
+ limit: 1
78
121
  });
79
122
 
80
- if (!email) {
123
+ if (!result.data.length) {
81
124
  return 0;
82
125
  }
83
126
 
84
- const count = email.get('csd_email_count');
127
+ const count = result.data[0].get('csd_email_count');
85
128
  return count || 0;
86
129
  }
87
130
 
@@ -94,12 +137,20 @@ export class DomainWarmingService {
94
137
  return WARMUP_SCALING_TABLE.base.value;
95
138
  }
96
139
 
140
+ // For high volume senders (400k+), cap the increase at 20% or 75k absolute
141
+ if (lastCount > WARMUP_SCALING_TABLE.highVolume.threshold) {
142
+ const scaledIncrease = Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale);
143
+ const absoluteIncrease = lastCount + WARMUP_SCALING_TABLE.highVolume.maxAbsoluteIncrease;
144
+ return Math.min(scaledIncrease, absoluteIncrease);
145
+ }
146
+
97
147
  for (const threshold of WARMUP_SCALING_TABLE.thresholds.sort((a, b) => a.limit - b.limit)) {
98
148
  if (lastCount <= threshold.limit) {
99
149
  return Math.ceil(lastCount * threshold.scale);
100
150
  }
101
151
  }
102
152
 
103
- return Math.ceil(lastCount * WARMUP_SCALING_TABLE.defaultScale);
153
+ // This should not be reached given the thresholds cover all cases up to highVolume.threshold
154
+ return Math.ceil(lastCount * WARMUP_SCALING_TABLE.highVolume.maxScale);
104
155
  }
105
156
  }
@@ -118,7 +118,8 @@ class EmailServiceWrapper {
118
118
 
119
119
  const domainWarmingService = new DomainWarmingService({
120
120
  models: {Email},
121
- labs
121
+ labs,
122
+ config: configService
122
123
  });
123
124
 
124
125
  const batchSendingService = new BatchSendingService({
@@ -8,20 +8,24 @@ let hasScheduled = {
8
8
 
9
9
  module.exports = {
10
10
  async scheduleMemberWelcomeEmailJob() {
11
- if (!config.get('memberWelcomeEmailTestInbox')) {
11
+ const testInboxDisabled = !config.get('memberWelcomeEmailTestInbox');
12
+ const alreadyScheduledProcessing = hasScheduled.processOutbox;
13
+
14
+ if (testInboxDisabled || alreadyScheduledProcessing) {
12
15
  return false;
13
16
  }
14
17
 
15
- if (!hasScheduled.processOutbox && !process.env.NODE_ENV.startsWith('test')) {
16
- jobsService.addJob({
17
- at: '0 */5 * * * *',
18
- job: path.resolve(__dirname, 'process-outbox.js'),
19
- name: 'process-member-welcome-emails'
20
- });
18
+ const configValue = config.get('memberWelcomeEmailSendInstantly');
19
+ const testEmailSendInstantly = configValue === true || configValue === 'true';
20
+ const cronSchedule = testEmailSendInstantly ? '*/3 * * * * *' : '0 */5 * * * *';
21
21
 
22
- hasScheduled.processOutbox = true;
23
- }
22
+ jobsService.addJob({
23
+ at: cronSchedule,
24
+ job: path.resolve(__dirname, 'process-outbox.js'),
25
+ name: 'process-member-welcome-emails'
26
+ });
24
27
 
28
+ hasScheduled.processOutbox = true;
25
29
  return hasScheduled.processOutbox;
26
30
  }
27
31
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost",
3
- "version": "6.9.0",
3
+ "version": "6.9.3",
4
4
  "description": "The professional publishing platform",
5
5
  "author": "Ghost Foundation",
6
6
  "homepage": "https://ghost.org",
@@ -87,7 +87,7 @@
87
87
  "@tryghost/helpers": "1.1.97",
88
88
  "@tryghost/html-to-plaintext": "1.0.4",
89
89
  "@tryghost/http-cache-utils": "0.1.20",
90
- "@tryghost/i18n": "file:components/tryghost-i18n-6.9.0.tgz",
90
+ "@tryghost/i18n": "file:components/tryghost-i18n-6.9.3.tgz",
91
91
  "@tryghost/image-transform": "1.4.6",
92
92
  "@tryghost/job-manager": "1.0.3",
93
93
  "@tryghost/kg-card-factory": "5.1.2",
@@ -274,7 +274,7 @@
274
274
  "jackspeak": "2.3.6",
275
275
  "moment": "2.24.0",
276
276
  "moment-timezone": "0.5.45",
277
- "@tryghost/i18n": "file:components/tryghost-i18n-6.9.0.tgz"
277
+ "@tryghost/i18n": "file:components/tryghost-i18n-6.9.3.tgz"
278
278
  },
279
279
  "nx": {
280
280
  "targets": {