Initial implementation of Azure Communication Service transport

Signed-off-by: Jonathan Mezach <jonathan.mezach@rr-wfm.com>
This commit is contained in:
Jonathan Mezach
2025-02-10 16:59:38 +01:00
parent bd52866722
commit 62d84a1a45
6 changed files with 163 additions and 23 deletions
+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,81 @@
/*
* 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) {
console.error(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';
+64 -23
View File
@@ -1656,18 +1656,50 @@ __metadata:
languageName: node
linkType: hard
"@azure/core-auth@npm:^1.4.0, @azure/core-auth@npm:^1.5.0":
version: 1.5.0
resolution: "@azure/core-auth@npm:1.5.0"
"@azure/communication-common@npm:^2.2.0":
version: 2.3.1
resolution: "@azure/communication-common@npm:2.3.1"
dependencies:
"@azure/abort-controller": ^1.0.0
"@azure/core-util": ^1.1.0
"@azure/core-auth": ^1.3.0
"@azure/core-rest-pipeline": ^1.3.2
"@azure/core-tracing": ^1.0.0
"@azure/core-util": ^1.0.0
events: ^3.0.0
jwt-decode: ^4.0.0
tslib: ^2.2.0
checksum: 11c5ba072902693435dc2930e2fdfe2ff34836f4c2d6c87c6ac0566d48dc49157ebf49f4478cd3784dc0c4d57b502d3a12d74ea29f416725204a6e1aa937ef78
checksum: de01172479bd0d498bccfd9906e9be7d57cf2b4c58159f140e573fd940c537fe0eba8d9fa8ee63469cb5c9f0c8d70bcac7afb330b514b4097a49df746367a255
languageName: node
linkType: hard
"@azure/core-client@npm:^1.3.0, @azure/core-client@npm:^1.6.2, @azure/core-client@npm:^1.9.2":
"@azure/communication-email@npm:^1.0.0":
version: 1.0.0
resolution: "@azure/communication-email@npm:1.0.0"
dependencies:
"@azure/communication-common": ^2.2.0
"@azure/core-auth": ^1.3.0
"@azure/core-client": ^1.3.2
"@azure/core-lro": ^2.5.0
"@azure/core-rest-pipeline": ^1.8.0
"@azure/logger": ^1.0.0
tslib: ^1.9.3
uuid: ^8.3.2
checksum: d939c729b303bc1bf7272b7f54850114f3cc90760cc0f796bbc8e01cd2834d0b28b33bd27b9ce590db691df94fbc23b10e39fa3c5d82e4b000a523616b5f12ef
languageName: node
linkType: hard
"@azure/core-auth@npm:^1.3.0, @azure/core-auth@npm:^1.4.0, @azure/core-auth@npm:^1.5.0, @azure/core-auth@npm:^1.8.0":
version: 1.9.0
resolution: "@azure/core-auth@npm:1.9.0"
dependencies:
"@azure/abort-controller": ^2.0.0
"@azure/core-util": ^1.11.0
tslib: ^2.6.2
checksum: 4050112188db093c5e01caca0175708c767054c0cea4202430ff43ee42a16430235752ccc0002caea1796c8f01b4f6369c878762bf4c1b2f61af1b7ac13182fc
languageName: node
linkType: hard
"@azure/core-client@npm:^1.3.0, @azure/core-client@npm:^1.3.2, @azure/core-client@npm:^1.6.2, @azure/core-client@npm:^1.9.2":
version: 1.9.2
resolution: "@azure/core-client@npm:1.9.2"
dependencies:
@@ -1693,15 +1725,15 @@ __metadata:
languageName: node
linkType: hard
"@azure/core-lro@npm:^2.2.0":
version: 2.5.4
resolution: "@azure/core-lro@npm:2.5.4"
"@azure/core-lro@npm:^2.2.0, @azure/core-lro@npm:^2.5.0":
version: 2.7.2
resolution: "@azure/core-lro@npm:2.7.2"
dependencies:
"@azure/abort-controller": ^1.0.0
"@azure/abort-controller": ^2.0.0
"@azure/core-util": ^1.2.0
"@azure/logger": ^1.0.0
tslib: ^2.2.0
checksum: f048b99850e8497b557cf661c2f8a384ea1227de6ea0c0e1436653851c3932e28a05056a380f7c20ebc51e4c6d7bd15d7dfabc6ecca80eddb9dc3e3339df9519
tslib: ^2.6.2
checksum: dc2e5bbb004a86704bcf584422cd099b7a6beef57ce6501afacced65f4f3b5fbba57a2439f701687237867552a661fd6568f8b3c9e3eacdfd9039004772f85b0
languageName: node
linkType: hard
@@ -1714,19 +1746,19 @@ __metadata:
languageName: node
linkType: hard
"@azure/core-rest-pipeline@npm:^1.1.0, @azure/core-rest-pipeline@npm:^1.10.1, @azure/core-rest-pipeline@npm:^1.3.0, @azure/core-rest-pipeline@npm:^1.9.1":
version: 1.16.1
resolution: "@azure/core-rest-pipeline@npm:1.16.1"
"@azure/core-rest-pipeline@npm:^1.1.0, @azure/core-rest-pipeline@npm:^1.10.1, @azure/core-rest-pipeline@npm:^1.3.0, @azure/core-rest-pipeline@npm:^1.3.2, @azure/core-rest-pipeline@npm:^1.8.0, @azure/core-rest-pipeline@npm:^1.9.1":
version: 1.19.0
resolution: "@azure/core-rest-pipeline@npm:1.19.0"
dependencies:
"@azure/abort-controller": ^2.0.0
"@azure/core-auth": ^1.4.0
"@azure/core-auth": ^1.8.0
"@azure/core-tracing": ^1.0.1
"@azure/core-util": ^1.9.0
"@azure/core-util": ^1.11.0
"@azure/logger": ^1.0.0
http-proxy-agent: ^7.0.0
https-proxy-agent: ^7.0.0
tslib: ^2.6.2
checksum: ab2c5b715c4ebb9f6554f12d8e804833be29db0f23868adc092f4841aa8c7f140a6821dbcd9fa046bfb31e435085f171a87dab3f97d851cf9a62127d042fa75e
checksum: e8d7631152d7af27b949f3da792a22a92a633b5744604f90e02e56e0b888e031f15f5d3384d7ca65ae851492ad0e33972f000b99600b081ceb0eb66e294454c1
languageName: node
linkType: hard
@@ -1739,13 +1771,13 @@ __metadata:
languageName: node
linkType: hard
"@azure/core-util@npm:^1.1.0, @azure/core-util@npm:^1.2.0, @azure/core-util@npm:^1.3.0, @azure/core-util@npm:^1.6.1, @azure/core-util@npm:^1.9.0":
version: 1.9.0
resolution: "@azure/core-util@npm:1.9.0"
"@azure/core-util@npm:^1.0.0, @azure/core-util@npm:^1.11.0, @azure/core-util@npm:^1.2.0, @azure/core-util@npm:^1.3.0, @azure/core-util@npm:^1.6.1":
version: 1.11.0
resolution: "@azure/core-util@npm:1.11.0"
dependencies:
"@azure/abort-controller": ^2.0.0
tslib: ^2.6.2
checksum: 9246dc5bd246e7b94883ea8130fce04e2f22abd1e94afcff7a3e92a4c2da5e9b382dbf89a606b21d70bc8b01c7c89c84e803ca9da27f78d87f72bdff91ec7380
checksum: 91e3ec329d9eddaa66be5efb1785dad68dcb48dd779fca36e39db041673230510158ff5ca9ccef9f19c3e4d8e9af29f66a367cfc31a7b94d2541f80ef94ec797
languageName: node
linkType: hard
@@ -7042,6 +7074,8 @@ __metadata:
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/backend-test-utils": "workspace:^"
"@backstage/catalog-client": "workspace:^"
@@ -34548,6 +34582,13 @@ __metadata:
languageName: node
linkType: hard
"jwt-decode@npm:^4.0.0":
version: 4.0.0
resolution: "jwt-decode@npm:4.0.0"
checksum: 390e2edcb31a92e86c8cbdd1edeea4c0d62acd371f8a8f0a8878e499390c0ecf4c658b365c4e941e4ef37d0170e4ca650aaa49f99a45c0b9695a235b210154b0
languageName: node
linkType: hard
"keygrip@npm:~1.1.0":
version: 1.1.0
resolution: "keygrip@npm:1.1.0"
@@ -45769,7 +45810,7 @@ __metadata:
languageName: node
linkType: hard
"tslib@npm:^1.11.1, tslib@npm:^1.14.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0":
"tslib@npm:^1.11.1, tslib@npm:^1.14.1, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3":
version: 1.14.1
resolution: "tslib@npm:1.14.1"
checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd