From 3df6c003aa8c44a4a1e114d47ef65e682822e3bb Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Tue, 15 Jul 2025 10:06:02 -0500 Subject: [PATCH] fix: add catch for rate limit errors --- apps/platform/src/providers/email/EmailError.ts | 3 +++ apps/platform/src/providers/email/EmailJob.ts | 8 +++++++- apps/platform/src/providers/email/EmailProvider.ts | 12 +++++++++++- .../src/subscriptions/SubscriptionController.ts | 4 ++-- 4 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 apps/platform/src/providers/email/EmailError.ts diff --git a/apps/platform/src/providers/email/EmailError.ts b/apps/platform/src/providers/email/EmailError.ts new file mode 100644 index 00000000..8d0fe68c --- /dev/null +++ b/apps/platform/src/providers/email/EmailError.ts @@ -0,0 +1,3 @@ +import { ContextError } from '../../error/ErrorHandler' + +export class RateLimitEmailError extends ContextError { } diff --git a/apps/platform/src/providers/email/EmailJob.ts b/apps/platform/src/providers/email/EmailJob.ts index 600e174a..73719961 100644 --- a/apps/platform/src/providers/email/EmailJob.ts +++ b/apps/platform/src/providers/email/EmailJob.ts @@ -1,4 +1,4 @@ -import Job from '../../queue/Job' +import Job, { RetryError } from '../../queue/Job' import { MessageTrigger } from '../MessageTrigger' import { updateSendState } from '../../campaigns/CampaignService' import { loadEmailChannel } from './index' @@ -7,6 +7,7 @@ import { EmailTemplate } from '../../render/Template' import { EncodedJob } from '../../queue' import App from '../../app' import { releaseLock } from '../../core/Lock' +import { RateLimitEmailError } from './EmailError' export default class EmailJob extends Job { static $name = 'email' @@ -42,6 +43,11 @@ export default class EmailJob extends Job { const result = await channel.send(template, data) await finalizeSend(data, result) } catch (error: any) { + + // If its a rate limit error, lets re-add to the queue to retry later + if (error instanceof RateLimitEmailError) { + throw new RetryError() + } await failSend(data, error) } finally { await releaseLock(messageLock(campaign, user)) diff --git a/apps/platform/src/providers/email/EmailProvider.ts b/apps/platform/src/providers/email/EmailProvider.ts index 0aae14c4..b5570d0e 100644 --- a/apps/platform/src/providers/email/EmailProvider.ts +++ b/apps/platform/src/providers/email/EmailProvider.ts @@ -2,6 +2,7 @@ import nodemailer from 'nodemailer' import { LoggerProviderName } from '../LoggerProvider' import Provider, { ProviderGroup } from '../Provider' import { Email } from './Email' +import { RateLimitEmailError } from './EmailError' export type EmailProviderName = 'ses' | 'smtp' | 'mailgun' | 'sendgrid' | LoggerProviderName @@ -14,7 +15,16 @@ export default abstract class EmailProvider extends Provider { static group = 'email' as ProviderGroup async send(message: Email): Promise { - return await this.transport?.sendMail(message) + try { + return await this.transport?.sendMail(message) + } catch (error: any) { + const isThrottle = error.code === 'Throttling' + || error.name === 'ThrottlingException' + || (error.message && error.message.includes('Throttling')) + || (error.cause && error.cause.name === 'ThrottlingException') + if (isThrottle) throw new RateLimitEmailError(error.message) + throw error + } } async verify(): Promise { diff --git a/apps/platform/src/subscriptions/SubscriptionController.ts b/apps/platform/src/subscriptions/SubscriptionController.ts index f05bf0a1..8de99859 100644 --- a/apps/platform/src/subscriptions/SubscriptionController.ts +++ b/apps/platform/src/subscriptions/SubscriptionController.ts @@ -144,8 +144,8 @@ const subscriptionPreferencesTemplate = compileTemplate {{#if subscriptions}}
-

Subscription Preferences

-

Choose which notifications you would like to continue to receive.

+

Communication Preferences

+

Choose which methods of communication you would like to continue to receive:

{{#if showUpdatedMessage}}
Your preferences have been updated!