Skip to content

Commit e8f1530

Browse files
authored
Merge pull request backstage#30370 from StateFarmIns/notifications-ses-config
fix: Add optional config for ses mail options with sourceArn, etc.
2 parents 72f52b6 + 3520a64 commit e8f1530

File tree

5 files changed

+109
-0
lines changed

5 files changed

+109
-0
lines changed

.changeset/tender-wolves-strive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@backstage/plugin-notifications-backend-module-email': patch
3+
---
4+
5+
Add optional config for `ses` mail options with `sourceArn`, `fromArn`, `configurationSetName`

plugins/notifications-backend-module-email/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ notifications:
7979
# Who to send email for broadcast notifications
8080
broadcastConfig:
8181
receiver: 'users'
82+
# Optional SES config
83+
# sesConfig:
84+
# sourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com'
85+
# fromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com'
86+
# configurationSetName: 'custom-config'
8287
# How many emails to send concurrently, defaults to 2
8388
concurrencyLimit: 10
8489
# How much to throttle between emails, defaults to 100ms

plugins/notifications-backend-module-email/config.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,23 @@ export interface Config {
132132
*/
133133
receiverEmails?: string[];
134134
};
135+
/**
136+
* Optional SES config for mail options. Allows for delegated sender
137+
*/
138+
sesConfig?: {
139+
/**
140+
* ARN of the identity to use as the source of the email
141+
*/
142+
sourceArn?: string;
143+
/**
144+
* ARN of the identity to use for the "From"/sender address of the email
145+
*/
146+
fromArn?: string;
147+
/**
148+
* Name of the configuration set to use when sending email via ses
149+
*/
150+
configurationSetName?: string;
151+
};
135152
cache?: {
136153
/**
137154
* Email cache TTL, defaults to 1 hour

plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,4 +442,64 @@ describe('NotificationsEmailProcessor', () => {
442442
443443
});
444444
});
445+
446+
it('should send email with ses config', async () => {
447+
const SES_SENDMAIL_CONFIG = {
448+
app: {
449+
baseUrl: 'https://example.org',
450+
},
451+
notifications: {
452+
processors: {
453+
email: {
454+
transportConfig: {
455+
transport: 'ses',
456+
region: 'us-west-2',
457+
},
458+
sender: '[email protected]',
459+
replyTo: '[email protected]',
460+
sesConfig: {
461+
sourceArn:
462+
'arn:aws:ses:us-west-2:123456789012:identity/example.com',
463+
fromArn:
464+
'arn:aws:ses:us-west-2:123456789012:identity/example.com',
465+
},
466+
},
467+
},
468+
},
469+
};
470+
(createTransport as jest.Mock).mockReturnValue(mockTransport);
471+
const processor = new NotificationsEmailProcessor(
472+
logger,
473+
mockServices.rootConfig({ data: SES_SENDMAIL_CONFIG }),
474+
catalogServiceMock({ entities: [DEFAULT_ENTITIES_RESPONSE.items[0]] }),
475+
auth,
476+
);
477+
478+
await processor.postProcess(
479+
{
480+
origin: 'plugin',
481+
id: '1234',
482+
user: 'user:default/mock',
483+
created: new Date(),
484+
payload: { title: 'notification' },
485+
},
486+
{
487+
recipients: { type: 'entity', entityRef: 'user:default/mock' },
488+
payload: { title: 'notification' },
489+
},
490+
);
491+
492+
expect(sendmailMock).toHaveBeenCalledWith({
493+
494+
html: '<p><a href="https://example.org/notifications">https://example.org/notifications</a></p>',
495+
replyTo: '[email protected]',
496+
subject: 'notification',
497+
text: 'https://example.org/notifications',
498+
499+
ses: {
500+
SourceArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com',
501+
FromArn: 'arn:aws:ses:us-west-2:123456789012:identity/example.com',
502+
},
503+
});
504+
});
445505
});

plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
5252
private readonly transportConfig: Config;
5353
private readonly sender: string;
5454
private readonly replyTo?: string;
55+
private readonly sesConfig?: Config;
5556
private readonly cacheTtl: number;
5657
private readonly concurrencyLimit: number;
5758
private readonly throttleInterval: number;
@@ -76,6 +77,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
7677
emailProcessorConfig.getOptionalConfig('broadcastConfig');
7778
this.sender = emailProcessorConfig.getString('sender');
7879
this.replyTo = emailProcessorConfig.getOptionalString('replyTo');
80+
this.sesConfig = emailProcessorConfig.getOptionalConfig('sesConfig');
7981
this.concurrencyLimit =
8082
emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2;
8183
this.throttleInterval = emailProcessorConfig.has('throttleInterval')
@@ -292,13 +294,32 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
292294
return contentParts.join('\n\n');
293295
}
294296

297+
private async getSesOptions() {
298+
if (!this.sesConfig) {
299+
return undefined;
300+
}
301+
const ses: Record<string, string> = {};
302+
const sourceArn = this.sesConfig.getOptionalString('sourceArn');
303+
const fromArn = this.sesConfig.getOptionalString('fromArn');
304+
const configurationSetName = this.sesConfig.getOptionalString(
305+
'configurationSetName',
306+
);
307+
308+
if (sourceArn) ses.SourceArn = sourceArn;
309+
if (fromArn) ses.FromArn = fromArn;
310+
if (configurationSetName) ses.ConfigurationSetName = configurationSetName;
311+
312+
return Object.keys(ses).length > 0 ? ses : undefined;
313+
}
314+
295315
private async sendPlainEmail(notification: Notification, emails: string[]) {
296316
const mailOptions = {
297317
from: this.sender,
298318
subject: notification.payload.title,
299319
html: this.getHtmlContent(notification),
300320
text: this.getTextContent(notification),
301321
replyTo: this.replyTo,
322+
ses: await this.getSesOptions(),
302323
};
303324

304325
await this.sendMails(mailOptions, emails);
@@ -316,6 +337,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
316337
html: await this.templateRenderer?.getHtml?.(notification),
317338
text: await this.templateRenderer?.getText?.(notification),
318339
replyTo: this.replyTo,
340+
ses: await this.getSesOptions(),
319341
};
320342

321343
await this.sendMails(mailOptions, emails);

0 commit comments

Comments
 (0)