Merge pull request #28796 from rr-wfm/feature/azure-communication-service

Add Azure Communication Service transport
This commit is contained in:
Ben Lambert
2025-02-18 09:26:52 +01:00
committed by GitHub
8 changed files with 174 additions and 24 deletions
@@ -2,7 +2,7 @@
Adds support for sending Backstage notifications as emails to users.
Supports sending emails using `SMTP`, `SES`, `sendmail`, or `stream` (for debugging purposes).
Supports sending emails using `SMTP`, `SES`, `azure`, `sendmail`, or `stream` (for debugging purposes).
## Customizing email content
@@ -61,6 +61,12 @@ notifications:
# accessKeyId: 'my-access-key
# region: 'us-west-2'
# Azure Communication Service
# transportConfig:
# transport: 'azure'
# endpoint: 'https://my-endpoint.communication.azure.com'
# accessKey: 'my-access-key' Optional: if not provided, Managed Identity will be used
# sendmail
# transportConfig:
# transport: 'sendmail'
+12
View File
@@ -83,6 +83,18 @@ export interface Config {
| {
/** Only for debugging, disables the actual sending of emails */
transport: 'stream';
}
| {
transport: 'azure';
/**
* Azure Communication Services endpoint
*/
endpoint: string;
/**
* Optional Azure Communication Services access key
* @visibility secret
*/
accessKey?: string;
};
/**
* Sender email address
@@ -36,6 +36,8 @@
"dependencies": {
"@aws-sdk/client-ses": "^3.550.0",
"@aws-sdk/types": "^3.347.0",
"@azure/communication-email": "^1.0.0",
"@azure/identity": "^4.0.0",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
@@ -32,6 +32,7 @@ import {
NotificationProcessorFilters,
} from '@backstage/plugin-notifications-common';
import {
createAzureTransport,
createSendmailTransport,
createSesTransport,
createSmtpTransport,
@@ -117,6 +118,8 @@ export class NotificationsEmailProcessor implements NotificationProcessor {
this.transporter = createSendmailTransport(this.transportConfig);
} else if (transport === 'stream') {
this.transporter = createStreamTransport();
} else if (transport === 'azure') {
this.transporter = createAzureTransport(this.transportConfig);
} else {
throw new Error(`Unsupported transport: ${transport}`);
}
@@ -0,0 +1,80 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createTransport } from 'nodemailer';
import {
EmailClient,
EmailMessage,
EmailRecipients,
} from '@azure/communication-email';
import { DefaultAzureCredential } from '@azure/identity';
import { Config } from '@backstage/config';
import MailMessage from 'nodemailer/lib/mailer/mail-message';
export const createAzureTransport = async (config: Config) => {
const accessKey = config.getOptionalString('accessKey');
const credentials =
accessKey === undefined ? new DefaultAzureCredential() : { key: accessKey };
const emailClient = new EmailClient(
config.getString('endpoint'),
credentials,
);
const transport = {
name: 'azure',
version: '1.0.0',
send: async (
mail: MailMessage,
callback: (err: Error | null, info: {} | null) => Promise<void>,
) => {
const envelope = mail.data.envelope || mail.message.getEnvelope();
const from =
typeof envelope.from === 'string'
? envelope.from
: config.getString('senderAddress');
const to =
typeof envelope.to === 'string' ? [envelope.to] : envelope.to ?? [];
const recipients: EmailRecipients = {
to: to.map(address => ({ address })),
};
const content = {
subject: mail.message.getHeader('Subject'),
html:
typeof mail.data.html === 'string'
? mail.data.html
: mail.data.html?.toString('utf-8'),
plainText:
typeof mail.data.text === 'string'
? mail.data.text
: mail.data.text?.toString('utf-8') ?? 'No content',
};
const emailMessage: EmailMessage = {
senderAddress: from,
recipients: recipients,
content: content,
};
try {
const poller = await emailClient.beginSend(emailMessage);
const response = await poller.pollUntilDone();
callback(null, response);
} catch (error) {
callback(error, null);
}
},
};
return createTransport(transport);
};
@@ -17,3 +17,4 @@ export { createSmtpTransport } from './smtp';
export { createSesTransport } from './ses';
export { createSendmailTransport } from './sendmail';
export { createStreamTransport } from './stream';
export { createAzureTransport } from './azure';