@push.rocks/smartmta 9.4.0 → 9.4.1

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.
@@ -148,6 +148,10 @@ export class MultiModeDeliverySystem extends EventEmitter {
148
148
  private emailServer?: UnifiedEmailServer;
149
149
  private bounceDrainTimer?: NodeJS.Timeout;
150
150
  private bounceDrainPromise?: Promise<void>;
151
+ private readonly queueItemsReadyHandler = (items: IQueueItem[]): Promise<void> =>
152
+ this.processItems(items);
153
+ private queueListenerRegistered = false;
154
+ private lifecycleGeneration = 0;
151
155
  private pendingSmtpAuthenticationUpdates = new Map<string, {
152
156
  itemId: string;
153
157
  transactionId: string;
@@ -225,8 +229,18 @@ export class MultiModeDeliverySystem extends EventEmitter {
225
229
  }
226
230
  };
227
231
 
228
- // Set up event listeners
229
- this.queue.on('itemsReady', this.processItems.bind(this));
232
+ }
233
+
234
+ private registerQueueListener(): void {
235
+ if (this.queueListenerRegistered) return;
236
+ this.queue.on('itemsReady', this.queueItemsReadyHandler);
237
+ this.queueListenerRegistered = true;
238
+ }
239
+
240
+ private unregisterQueueListener(): void {
241
+ if (!this.queueListenerRegistered) return;
242
+ this.queue.off('itemsReady', this.queueItemsReadyHandler);
243
+ this.queueListenerRegistered = false;
230
244
  }
231
245
 
232
246
  private createEmailForRecipients(email: Email, recipients: string[]): Email {
@@ -510,14 +524,33 @@ export class MultiModeDeliverySystem extends EventEmitter {
510
524
  return;
511
525
  }
512
526
 
527
+ const lifecycleGeneration = ++this.lifecycleGeneration;
513
528
  this.running = true;
514
- await this.drainBounceOutbox();
515
- this.bounceDrainTimer = setInterval(() => {
516
- void this.drainBounceOutbox();
517
- }, 30_000);
518
-
519
- await emitSafely(this, 'started');
520
- logger.log('info', 'MultiModeDeliverySystem started successfully');
529
+ this.registerQueueListener();
530
+ try {
531
+ await this.drainBounceOutbox();
532
+ if (!this.running || lifecycleGeneration !== this.lifecycleGeneration) return;
533
+
534
+ this.bounceDrainTimer = setInterval(() => {
535
+ void this.drainBounceOutbox();
536
+ }, 30_000);
537
+
538
+ await emitSafely(this, 'started');
539
+ if (!this.running || lifecycleGeneration !== this.lifecycleGeneration) return;
540
+
541
+ this.queue.wake();
542
+ logger.log('info', 'MultiModeDeliverySystem started successfully');
543
+ } catch (error) {
544
+ if (lifecycleGeneration === this.lifecycleGeneration) {
545
+ this.running = false;
546
+ this.unregisterQueueListener();
547
+ if (this.bounceDrainTimer) {
548
+ clearInterval(this.bounceDrainTimer);
549
+ this.bounceDrainTimer = undefined;
550
+ }
551
+ }
552
+ throw error;
553
+ }
521
554
  }
522
555
 
523
556
  /**
@@ -527,11 +560,15 @@ export class MultiModeDeliverySystem extends EventEmitter {
527
560
  logger.log('info', 'Stopping MultiModeDeliverySystem');
528
561
 
529
562
  if (!this.running) {
563
+ this.unregisterQueueListener();
530
564
  logger.log('warn', 'MultiModeDeliverySystem is already stopped');
531
565
  return;
532
566
  }
567
+
568
+ const lifecycleGeneration = ++this.lifecycleGeneration;
533
569
 
534
570
  this.running = false;
571
+ this.unregisterQueueListener();
535
572
  if (this.bounceDrainTimer) {
536
573
  clearInterval(this.bounceDrainTimer);
537
574
  this.bounceDrainTimer = undefined;
@@ -542,6 +579,10 @@ export class MultiModeDeliverySystem extends EventEmitter {
542
579
  clearTimeout(this.throttleResetTimer);
543
580
  this.throttleResetTimer = null;
544
581
  }
582
+ this.throttled = false;
583
+ this.rateLimitCounter = 0;
584
+ this.rateLimitLastCheck = Date.now();
585
+ this.stats.rateLimiting.currentRate = 0;
545
586
 
546
587
  // Wait for active deliveries to complete
547
588
  if (this.activeDeliveries.size > 0) {
@@ -566,6 +607,8 @@ export class MultiModeDeliverySystem extends EventEmitter {
566
607
  }
567
608
 
568
609
  await this.bounceDrainPromise;
610
+ if (lifecycleGeneration !== this.lifecycleGeneration) return;
611
+
569
612
  await emitSafely(this, 'stopped');
570
613
  logger.log('info', 'MultiModeDeliverySystem stopped successfully');
571
614
  }
@@ -585,17 +628,15 @@ export class MultiModeDeliverySystem extends EventEmitter {
585
628
  return;
586
629
  }
587
630
 
588
- // Check rate limiting
589
- if (this.checkRateLimit()) {
590
- logger.log('debug', 'Rate limit exceeded, throttling deliveries');
591
- return;
592
- }
593
-
594
631
  let startedDeliveries = 0;
595
632
 
596
633
  for (const item of items) {
597
634
  if (this.activeDeliveries.size >= this.options.concurrentDeliveries) break;
598
635
  if (this.activeDeliveries.has(item.id)) continue;
636
+ if (this.checkRateLimit()) {
637
+ logger.log('debug', 'Rate limit exceeded, throttling deliveries');
638
+ break;
639
+ }
599
640
 
600
641
  // Reserve synchronously before the first await so overlapping queue events
601
642
  // cannot dispatch the same item while its durable claim is in flight.
@@ -818,6 +859,7 @@ export class MultiModeDeliverySystem extends EventEmitter {
818
859
 
819
860
  // Update statistics
820
861
  await emitSafely(this, 'statsUpdated', this.stats);
862
+ this.queue.wake();
821
863
  }
822
864
  }
823
865
 
@@ -1403,7 +1445,6 @@ export class MultiModeDeliverySystem extends EventEmitter {
1403
1445
  this.rateLimitCounter = 0;
1404
1446
  this.throttled = false;
1405
1447
  this.stats.rateLimiting.currentRate = 0;
1406
- return false;
1407
1448
  }
1408
1449
 
1409
1450
  // Check if we're already throttled
@@ -1411,15 +1452,9 @@ export class MultiModeDeliverySystem extends EventEmitter {
1411
1452
  return true;
1412
1453
  }
1413
1454
 
1414
- // Increment counter
1415
- this.rateLimitCounter++;
1416
-
1417
- // Calculate current rate (emails per minute)
1418
- const rate = (this.rateLimitCounter / elapsed) * 60000;
1419
- this.stats.rateLimiting.currentRate = rate;
1420
-
1421
- // Check if rate limit is exceeded
1422
- if (rate > this.options.globalRateLimit) {
1455
+ // Use a fixed one-minute window. Extrapolating the first delivery from a
1456
+ // few elapsed milliseconds falsely throttles every freshly started queue.
1457
+ if (this.rateLimitCounter >= this.options.globalRateLimit) {
1423
1458
  this.throttled = true;
1424
1459
  this.stats.rateLimiting.throttled++;
1425
1460
 
@@ -1434,10 +1469,16 @@ export class MultiModeDeliverySystem extends EventEmitter {
1434
1469
  this.rateLimitLastCheck = Date.now();
1435
1470
  this.rateLimitCounter = 0;
1436
1471
  this.stats.rateLimiting.currentRate = 0;
1472
+ if (this.running) {
1473
+ this.queue.wake();
1474
+ }
1437
1475
  }, resetDelay);
1438
1476
 
1439
1477
  return true;
1440
1478
  }
1479
+
1480
+ this.rateLimitCounter++;
1481
+ this.stats.rateLimiting.currentRate = this.rateLimitCounter;
1441
1482
 
1442
1483
  return false;
1443
1484
  }