From 49295af2c16e50b259ec39e9c0c68ed8c0d11d13 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Wed, 24 Apr 2024 10:30:28 +0300 Subject: [PATCH] feat: allow throttling email sending Signed-off-by: Heikki Hellgren --- .../config.d.ts | 4 ++++ .../package.json | 2 +- .../processor/NotificationsEmailProcessor.ts | 19 ++++++++++++++----- yarn.lock | 9 ++++++++- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 90fb43e046..db49d0016a 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -91,6 +91,10 @@ export interface Config { * Concurrency limit for email sending, defaults to 2 */ concurrencyLimit?: number; + /** + * Throttle duration between email sending, defaults to 100ms + */ + throttleInterval?: HumanDuration; /** * Configuration for broadcast notifications */ diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 3d44db6a45..dfff036a13 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -45,7 +45,7 @@ "@backstage/types": "workspace:^", "lodash": "^4.17.21", "nodemailer": "^6.9.13", - "p-limit": "^3.1.0" + "p-throttle": "^6.1.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index a52495a8ea..d9f6b73e0a 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -39,7 +39,7 @@ import { compact } from 'lodash'; import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; import { NotificationTemplateRenderer } from '../extensions'; import Mail from 'nodemailer/lib/mailer'; -import pLimit from 'p-limit'; +import pThrottle from 'p-throttle'; export class NotificationsEmailProcessor implements NotificationProcessor { private transporter: any; @@ -49,6 +49,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly replyTo?: string; private readonly cacheTtl: number; private readonly concurrencyLimit: number; + private readonly throttleInterval: number; constructor( private readonly logger: LoggerService, @@ -68,6 +69,11 @@ export class NotificationsEmailProcessor implements NotificationProcessor { this.replyTo = emailProcessorConfig.getOptionalString('replyTo'); this.concurrencyLimit = emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2; + const throttleConfig = + emailProcessorConfig.getOptionalConfig('throttleInterval'); + this.throttleInterval = throttleConfig + ? durationToMilliseconds(readDurationFromConfig(throttleConfig)) + : 100; const cacheConfig = emailProcessorConfig.getOptionalConfig('cache.ttl'); this.cacheTtl = cacheConfig ? durationToMilliseconds(readDurationFromConfig(cacheConfig)) @@ -198,11 +204,14 @@ export class NotificationsEmailProcessor implements NotificationProcessor { } private async sendMails(options: Mail.Options, emails: string[]) { - const limit = pLimit(this.concurrencyLimit); + const throttle = pThrottle({ + limit: this.concurrencyLimit, + interval: this.throttleInterval, + }); + + const throttled = throttle((opts: Mail.Options) => this.sendMail(opts)); await Promise.all( - emails.map(email => - limit(() => this.sendMail({ ...options, to: email })), - ), + emails.map(email => throttled({ ...options, to: email })), ); } diff --git a/yarn.lock b/yarn.lock index 400095afee..846721560c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6051,7 +6051,7 @@ __metadata: "@types/nodemailer": ^6.4.14 lodash: ^4.17.21 nodemailer: ^6.9.13 - p-limit: ^3.1.0 + p-throttle: ^6.1.0 languageName: unknown linkType: soft @@ -33255,6 +33255,13 @@ __metadata: languageName: node linkType: hard +"p-throttle@npm:^6.1.0": + version: 6.1.0 + resolution: "p-throttle@npm:6.1.0" + checksum: c1947cca8844564c3d86f8c09067add5e7398b87898cb1f1aea5c48d2c211590d039a316d28a4b0d95364ffa0213c01dff6bf71175c468eab0e381f77715dbdb + languageName: node + linkType: hard + "p-timeout@npm:^3.2.0": version: 3.2.0 resolution: "p-timeout@npm:3.2.0"