From 7b65adeaaa744460ad9d95df7b780bd5c1f26449 Mon Sep 17 00:00:00 2001 From: Colt McKissick Date: Wed, 26 Nov 2025 11:58:17 -0500 Subject: [PATCH 1/6] fix: Update email processor ses options to match v2 Signed-off-by: Colt McKissick --- .changeset/fancy-wasps-check.md | 5 +++++ .../config.d.ts | 6 +----- .../NotificationsEmailProcessor.test.ts | 8 +++----- .../src/processor/NotificationsEmailProcessor.ts | 16 ++++++++++------ 4 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 .changeset/fancy-wasps-check.md diff --git a/.changeset/fancy-wasps-check.md b/.changeset/fancy-wasps-check.md new file mode 100644 index 0000000000..fb7fbfed73 --- /dev/null +++ b/.changeset/fancy-wasps-check.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-email': minor +--- + +Changes ses configuration keys to match new configuration options in SES SDK V2 diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 7116826c5d..f73ef1074b 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -136,14 +136,10 @@ export interface Config { * Optional SES config for mail options. Allows for delegated sender */ sesConfig?: { - /** - * ARN of the identity to use as the source of the email - */ - sourceArn?: string; /** * ARN of the identity to use for the "From"/sender address of the email */ - fromArn?: string; + fromEmailAddressIdentityArn?: string; /** * Name of the configuration set to use when sending email via ses */ diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index ebe1b90f35..4ebc5d40a8 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -458,9 +458,7 @@ describe('NotificationsEmailProcessor', () => { sender: 'backstage@backstage.io', replyTo: 'no-reply@backstage.io', sesConfig: { - sourceArn: - 'arn:aws:ses:us-west-2:123456789012:identity/example.com', - fromArn: + fromEmailAddressIdentityArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', }, }, @@ -497,8 +495,8 @@ describe('NotificationsEmailProcessor', () => { text: 'https://example.org/notifications', to: 'mock@backstage.io', ses: { - SourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', - FromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', + FromEmailAddressIdentityArn: + 'arn:aws:ses:us-west-2:123456789012:identity/example.com', }, }); }); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index eadfe7747e..bec19e77e8 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -45,6 +45,7 @@ import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; import { NotificationTemplateRenderer } from '../extensions'; import Mail from 'nodemailer/lib/mailer'; import pThrottle from 'p-throttle'; +import { SendEmailCommandInput } from '@aws-sdk/client-sesv2'; export class NotificationsEmailProcessor implements NotificationProcessor { private transporter: any; @@ -307,19 +308,22 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return contentParts.join('\n\n'); } - private async getSesOptions() { + private async getSesOptions(): Promise< + Partial | undefined + > { if (!this.sesConfig) { return undefined; } - const ses: Record = {}; - const sourceArn = this.sesConfig.getOptionalString('sourceArn'); - const fromArn = this.sesConfig.getOptionalString('fromArn'); + const ses: Partial = {}; + const fromEmailAddressIdentityArn = this.sesConfig.getOptionalString( + 'fromEmailAddressIdentityArn', + ); const configurationSetName = this.sesConfig.getOptionalString( 'configurationSetName', ); - if (sourceArn) ses.SourceArn = sourceArn; - if (fromArn) ses.FromArn = fromArn; + if (fromEmailAddressIdentityArn) + ses.FromEmailAddressIdentityArn = fromEmailAddressIdentityArn; if (configurationSetName) ses.ConfigurationSetName = configurationSetName; return Object.keys(ses).length > 0 ? ses : undefined; From 3513cdd4f7acd3cc24115716d6e458fd736447c2 Mon Sep 17 00:00:00 2001 From: Colt McKissick Date: Mon, 1 Dec 2025 11:15:26 -0500 Subject: [PATCH 2/6] docs: update readme example to reflect config changes Signed-off-by: Colt McKissick --- plugins/notifications-backend-module-email/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md index c1ead48876..d56d277544 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -81,8 +81,7 @@ notifications: receiver: 'users' # Optional SES config # sesConfig: - # sourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' - # fromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' + # fromEmailAddressIdentityArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' # configurationSetName: 'custom-config' # How many emails to send concurrently, defaults to 2 concurrencyLimit: 10 From a5d5b3adcc04d855791f7be957b241ad1b561817 Mon Sep 17 00:00:00 2001 From: Colt McKissick Date: Tue, 2 Dec 2025 09:21:29 -0500 Subject: [PATCH 3/6] fix: mark fromArn as deprecated, update getSesOptions to read fromArn if set Signed-off-by: Colt McKissick --- .changeset/curvy-things-call.md | 22 +++++++ .../config.d.ts | 5 ++ .../NotificationsEmailProcessor.test.ts | 58 +++++++++++++++++++ .../processor/NotificationsEmailProcessor.ts | 3 + 4 files changed, 88 insertions(+) create mode 100644 .changeset/curvy-things-call.md diff --git a/.changeset/curvy-things-call.md b/.changeset/curvy-things-call.md new file mode 100644 index 0000000000..1f4d8774c7 --- /dev/null +++ b/.changeset/curvy-things-call.md @@ -0,0 +1,22 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +--- + +SES config for the notification email processor now supports sending an ARN for the SES identity to use when sending an email after the SES SDK V2 update. + +The `sesConfig.fromArn` field is marked as deprecated in favor of `sesConfig.fromEmailAddressIdentityArn` to match the option name passed during the send email command. Currently both `sesConfig.fromArn` and `sesConfig.fromEmailAddressIdentityArn` will set the `fromEmailAddressIdentityArn` option. The `sesConfig.sourceArn` field is removed since no equivalent option is available in the send email command options. Example using `sesConfig.fromEmailAddressIdentityArn`: + +```diff +notifications: + processors: + email: + transportConfig: + transport: "ses" + region: "us-west-2" + sender: "sender@mycompany.com" + replyTo: "no-reply@mycompany.com" + sesConfig: +- sourceArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" +- fromArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" ++ fromEmailAddressIdentityArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" +``` diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index f73ef1074b..350fb569c5 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -136,6 +136,11 @@ export interface Config { * Optional SES config for mail options. Allows for delegated sender */ sesConfig?: { + /** + * ARN of the identity to use for the "From"/sender address of the email + * @deprecated Use fromEmailAddressIdentityArn instead + */ + fromArn?: string; /** * ARN of the identity to use for the "From"/sender address of the email */ diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index 4ebc5d40a8..a52b54401e 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -500,4 +500,62 @@ describe('NotificationsEmailProcessor', () => { }, }); }); + + it('should send email with deprecated ses config', async () => { + const SES_SENDMAIL_CONFIG = { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + email: { + transportConfig: { + transport: 'ses', + region: 'us-west-2', + }, + sender: 'backstage@backstage.io', + replyTo: 'no-reply@backstage.io', + sesConfig: { + fromArn: + 'arn:aws:ses:us-west-2:123456789012:identity/example.com', + }, + }, + }, + }, + }; + (createTransport as jest.Mock).mockReturnValue(mockTransport); + const processor = new NotificationsEmailProcessor( + logger, + mockServices.rootConfig({ data: SES_SENDMAIL_CONFIG }), + catalogServiceMock({ entities: [DEFAULT_ENTITIES_RESPONSE.items[0]] }), + auth, + ); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '

https://example.org/notifications

', + replyTo: 'no-reply@backstage.io', + subject: 'notification', + text: 'https://example.org/notifications', + to: 'mock@backstage.io', + ses: { + FromEmailAddressIdentityArn: + 'arn:aws:ses:us-west-2:123456789012:identity/example.com', + }, + }); + }); }); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index bec19e77e8..acf1fbdb4c 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -318,12 +318,15 @@ export class NotificationsEmailProcessor implements NotificationProcessor { const fromEmailAddressIdentityArn = this.sesConfig.getOptionalString( 'fromEmailAddressIdentityArn', ); + const fromArn = this.sesConfig.getOptionalString('fromArn'); const configurationSetName = this.sesConfig.getOptionalString( 'configurationSetName', ); if (fromEmailAddressIdentityArn) ses.FromEmailAddressIdentityArn = fromEmailAddressIdentityArn; + else if (fromArn) ses.FromEmailAddressIdentityArn = fromArn; + if (configurationSetName) ses.ConfigurationSetName = configurationSetName; return Object.keys(ses).length > 0 ? ses : undefined; From 625457ab4d148cf66fdfafdf312121ccaeec7ef5 Mon Sep 17 00:00:00 2001 From: Colt McKissick Date: Tue, 2 Dec 2025 09:22:43 -0500 Subject: [PATCH 4/6] chore: remove old changeset Signed-off-by: Colt McKissick --- .changeset/fancy-wasps-check.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/fancy-wasps-check.md diff --git a/.changeset/fancy-wasps-check.md b/.changeset/fancy-wasps-check.md deleted file mode 100644 index fb7fbfed73..0000000000 --- a/.changeset/fancy-wasps-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-notifications-backend-module-email': minor ---- - -Changes ses configuration keys to match new configuration options in SES SDK V2 From 09c498aa68c282fa7662297bd4978572d979a5e6 Mon Sep 17 00:00:00 2001 From: Colt McKissick Date: Tue, 2 Dec 2025 10:30:16 -0500 Subject: [PATCH 5/6] fix: use fromArn config, log warning for sourceArn Signed-off-by: Colt McKissick --- .changeset/curvy-things-call.md | 7 +-- .../README.md | 2 +- .../config.d.ts | 5 -- .../NotificationsEmailProcessor.test.ts | 62 ++----------------- .../processor/NotificationsEmailProcessor.ts | 13 ++-- 5 files changed, 15 insertions(+), 74 deletions(-) diff --git a/.changeset/curvy-things-call.md b/.changeset/curvy-things-call.md index 1f4d8774c7..d64f1a14fa 100644 --- a/.changeset/curvy-things-call.md +++ b/.changeset/curvy-things-call.md @@ -2,9 +2,9 @@ '@backstage/plugin-notifications-backend-module-email': patch --- -SES config for the notification email processor now supports sending an ARN for the SES identity to use when sending an email after the SES SDK V2 update. +SES config for the notification email processor now supports utilizing an ARN for the SES identity when sending an email after the SES SDK V2 update. -The `sesConfig.fromArn` field is marked as deprecated in favor of `sesConfig.fromEmailAddressIdentityArn` to match the option name passed during the send email command. Currently both `sesConfig.fromArn` and `sesConfig.fromEmailAddressIdentityArn` will set the `fromEmailAddressIdentityArn` option. The `sesConfig.sourceArn` field is removed since no equivalent option is available in the send email command options. Example using `sesConfig.fromEmailAddressIdentityArn`: +The `sesConfig.fromArn` will set the `fromEmailAddressIdentityArn` option for the SES `SendEmailCommand`. The `sesConfig.sourceArn` field is removed since no equivalent option is available in the send email command options. Setting `sesConfig.sourceArn` will have no effect and log a warning. Example changes: ```diff notifications: @@ -17,6 +17,5 @@ notifications: replyTo: "no-reply@mycompany.com" sesConfig: - sourceArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" -- fromArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" -+ fromEmailAddressIdentityArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" + fromArn: "arn:aws:ses:us-west-2:123456789012:identity/example.com" ``` diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md index d56d277544..248bb2c501 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -81,7 +81,7 @@ notifications: receiver: 'users' # Optional SES config # sesConfig: - # fromEmailAddressIdentityArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' + # fromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com' # configurationSetName: 'custom-config' # How many emails to send concurrently, defaults to 2 concurrencyLimit: 10 diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 350fb569c5..55a751f8fa 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -138,13 +138,8 @@ export interface Config { sesConfig?: { /** * ARN of the identity to use for the "From"/sender address of the email - * @deprecated Use fromEmailAddressIdentityArn instead */ fromArn?: string; - /** - * ARN of the identity to use for the "From"/sender address of the email - */ - fromEmailAddressIdentityArn?: string; /** * Name of the configuration set to use when sending email via ses */ diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index a52b54401e..ad0817521f 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -458,64 +458,8 @@ describe('NotificationsEmailProcessor', () => { sender: 'backstage@backstage.io', replyTo: 'no-reply@backstage.io', sesConfig: { - fromEmailAddressIdentityArn: + sourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', - }, - }, - }, - }, - }; - (createTransport as jest.Mock).mockReturnValue(mockTransport); - const processor = new NotificationsEmailProcessor( - logger, - mockServices.rootConfig({ data: SES_SENDMAIL_CONFIG }), - catalogServiceMock({ entities: [DEFAULT_ENTITIES_RESPONSE.items[0]] }), - auth, - ); - - await processor.postProcess( - { - origin: 'plugin', - id: '1234', - user: 'user:default/mock', - created: new Date(), - payload: { title: 'notification' }, - }, - { - recipients: { type: 'entity', entityRef: 'user:default/mock' }, - payload: { title: 'notification' }, - }, - ); - - expect(sendmailMock).toHaveBeenCalledWith({ - from: 'backstage@backstage.io', - html: '

https://example.org/notifications

', - replyTo: 'no-reply@backstage.io', - subject: 'notification', - text: 'https://example.org/notifications', - to: 'mock@backstage.io', - ses: { - FromEmailAddressIdentityArn: - 'arn:aws:ses:us-west-2:123456789012:identity/example.com', - }, - }); - }); - - it('should send email with deprecated ses config', async () => { - const SES_SENDMAIL_CONFIG = { - app: { - baseUrl: 'https://example.org', - }, - notifications: { - processors: { - email: { - transportConfig: { - transport: 'ses', - region: 'us-west-2', - }, - sender: 'backstage@backstage.io', - replyTo: 'no-reply@backstage.io', - sesConfig: { fromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com', }, @@ -557,5 +501,9 @@ describe('NotificationsEmailProcessor', () => { 'arn:aws:ses:us-west-2:123456789012:identity/example.com', }, }); + + expect(logger.warn).toHaveBeenCalledWith( + 'sourceArn is not supported in SESv2 and will be ignored', + ); }); }); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index acf1fbdb4c..7eea12bd85 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -315,19 +315,18 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return undefined; } const ses: Partial = {}; - const fromEmailAddressIdentityArn = this.sesConfig.getOptionalString( - 'fromEmailAddressIdentityArn', - ); const fromArn = this.sesConfig.getOptionalString('fromArn'); + const sourceArn = this.sesConfig.getOptionalString('sourceArn'); const configurationSetName = this.sesConfig.getOptionalString( 'configurationSetName', ); - if (fromEmailAddressIdentityArn) - ses.FromEmailAddressIdentityArn = fromEmailAddressIdentityArn; - else if (fromArn) ses.FromEmailAddressIdentityArn = fromArn; - + if (fromArn) ses.FromEmailAddressIdentityArn = fromArn; if (configurationSetName) ses.ConfigurationSetName = configurationSetName; + if (sourceArn) + this.logger.warn( + 'sourceArn is not supported in SESv2 and will be ignored', + ); return Object.keys(ses).length > 0 ? ses : undefined; } From 8dd4ce407359dd9dc8525dccff84433ff74aeabe Mon Sep 17 00:00:00 2001 From: Colt McKissick Date: Wed, 3 Dec 2025 10:13:41 -0500 Subject: [PATCH 6/6] refactor: populate ses options during constructor Signed-off-by: Colt McKissick --- .../src/processor/NotificationsEmailProcessor.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 7eea12bd85..e0e27232c2 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -54,6 +54,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly sender: string; private readonly replyTo?: string; private readonly sesConfig?: Config; + private readonly sesOptions?: Partial; private readonly cacheTtl: number; private readonly concurrencyLimit: number; private readonly throttleInterval: number; @@ -92,6 +93,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { this.sender = emailProcessorConfig.getString('sender'); this.replyTo = emailProcessorConfig.getOptionalString('replyTo'); this.sesConfig = emailProcessorConfig.getOptionalConfig('sesConfig'); + this.sesOptions = this.getSesOptions(); this.concurrencyLimit = emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2; this.throttleInterval = emailProcessorConfig.has('throttleInterval') @@ -308,9 +310,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return contentParts.join('\n\n'); } - private async getSesOptions(): Promise< - Partial | undefined - > { + private getSesOptions(): Partial | undefined { if (!this.sesConfig) { return undefined; } @@ -338,7 +338,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { html: this.getHtmlContent(notification), text: this.getTextContent(notification), replyTo: this.replyTo, - ses: await this.getSesOptions(), + ses: this.sesOptions, }; await this.sendMails(mailOptions, emails); @@ -356,7 +356,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { html: await this.templateRenderer?.getHtml?.(notification), text: await this.templateRenderer?.getText?.(notification), replyTo: this.replyTo, - ses: await this.getSesOptions(), + ses: this.sesOptions, }; await this.sendMails(mailOptions, emails);