@backstage/plugin-notifications-backend-module-email 0.0.0-nightly-20240426021211

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ # @backstage/plugin-notifications-backend-module-email
2
+
3
+ ## 0.0.0-nightly-20240426021211
4
+
5
+ ### Patch Changes
6
+
7
+ - dbf2696: Allow sending notifications by email with the new notifications module
8
+ - Updated dependencies
9
+ - @backstage/catalog-model@0.0.0-nightly-20240426021211
10
+ - @backstage/plugin-notifications-node@0.0.0-nightly-20240426021211
11
+ - @backstage/backend-common@0.0.0-nightly-20240426021211
12
+ - @backstage/backend-plugin-api@0.0.0-nightly-20240426021211
13
+ - @backstage/catalog-client@0.0.0-nightly-20240426021211
14
+ - @backstage/config@1.2.0
15
+ - @backstage/integration-aws-node@0.1.12
16
+ - @backstage/types@1.1.1
17
+ - @backstage/plugin-notifications-common@0.0.3
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # @backstage/plugin-notifications-backend-module-email
2
+
3
+ Adds support for sending Backstage notifications as emails to users.
4
+
5
+ Supports sending emails using `SMTP`, `SES`, or `sendmail`.
6
+
7
+ ## Customizing email content
8
+
9
+ The email content can be customized with the `notificationsEmailTemplateExtensionPoint`. When you create
10
+ this extension, you can set the custom `NotificationTemplateRenderer` to the module. To modify the contents,
11
+ override the `getSubject`, `getHtml` and `getText` methods.
12
+
13
+ ```ts
14
+ import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email';
15
+ import { Notification } from '@backstage/plugin-notifications-common';
16
+
17
+ export const notificationsModuleEmailDecorator = createBackendModule({
18
+ pluginId: 'notifications',
19
+ moduleId: 'email.templates',
20
+ register(reg) {
21
+ reg.registerInit({
22
+ deps: {
23
+ emailTemplates: notificationsEmailTemplateExtensionPoint,
24
+ },
25
+ async init({ emailTemplates }) {
26
+ emailTemplates.setTemplateRenderer({
27
+ getSubject(notification) {
28
+ return `New notification from ${notification.source}`;
29
+ },
30
+ getText(notification) {
31
+ return notification.content;
32
+ },
33
+ getHtml(notification) {
34
+ return `<p>${notification.content}</p>`;
35
+ },
36
+ });
37
+ },
38
+ });
39
+ },
40
+ });
41
+ ```
42
+
43
+ ## Example configuration:
44
+
45
+ ```yaml
46
+ notifications:
47
+ processors:
48
+ email:
49
+ # Transport config, see options at `config.d.ts`
50
+ transportConfig:
51
+ transport: 'smtp'
52
+ hostname: 'my-smtp-server'
53
+ port: 587
54
+ secure: false
55
+ username: 'my-username'
56
+ password: 'my-password'
57
+ # The email sender address
58
+ sender: 'sender@mycompany.com'
59
+ replyTo: 'no-reply@mycompany.com'
60
+ # Who to get email for broadcast notifications
61
+ broadcastConfig:
62
+ receiver: 'users'
63
+ # How many emails to send concurrently, defaults to 2
64
+ concurrencyLimit: 10
65
+ # Cache configuration for email addresses
66
+ # This is to prevent unnecessary calls to the catalog
67
+ cache:
68
+ ttl:
69
+ days: 1
70
+ ```
71
+
72
+ See `config.d.ts` for more options for configuration.
package/config.d.ts ADDED
@@ -0,0 +1,123 @@
1
+ /*
2
+ * Copyright 2024 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { HumanDuration } from '@backstage/types';
18
+
19
+ export interface Config {
20
+ /**
21
+ * Configuration options for notifications-backend-module-email */
22
+ notifications: {
23
+ processors: {
24
+ email: {
25
+ /**
26
+ * Transport to use for sending emails */
27
+ transportConfig:
28
+ | {
29
+ transport: 'smtp';
30
+ /**
31
+ * SMTP server hostname
32
+ */
33
+ hostname: string;
34
+ /**
35
+ * SMTP server port
36
+ */
37
+ port: number;
38
+ /**
39
+ * Use secure connection for SMTP, defaults to false
40
+ */
41
+ secure?: boolean;
42
+ /**
43
+ * Require TLS for SMTP connection, defaults to false
44
+ */
45
+ requireTls?: boolean;
46
+ /**
47
+ * SMTP username
48
+ */
49
+ username?: string;
50
+ /**
51
+ * SMTP password
52
+ * @visibility secret
53
+ */
54
+ password?: string;
55
+ }
56
+ | {
57
+ transport: 'ses';
58
+ /**
59
+ * SES ApiVersion to use, defaults to 2010-12-01
60
+ */
61
+ apiVersion?: string;
62
+ /**
63
+ * AWS account ID to use
64
+ */
65
+ accountId?: string;
66
+ /**
67
+ * AWS region to use
68
+ */
69
+ region?: string;
70
+ }
71
+ | {
72
+ transport: 'sendmail';
73
+ /**
74
+ * Sendmail binary path, defaults to /usr/sbin/sendmail
75
+ */
76
+ path?: string;
77
+ /**
78
+ * Newline style, defaults to 'unix'
79
+ */
80
+ newline?: 'unix' | 'windows';
81
+ };
82
+ /**
83
+ * Sender email address
84
+ */
85
+ sender: string;
86
+ /**
87
+ * Optional reply-to address
88
+ */
89
+ replyTo?: string;
90
+ /**
91
+ * Concurrency limit for email sending, defaults to 2
92
+ */
93
+ concurrencyLimit?: number;
94
+ /**
95
+ * Throttle duration between email sending, defaults to 100ms
96
+ */
97
+ throttleInterval?: HumanDuration;
98
+ /**
99
+ * Configuration for broadcast notifications
100
+ */
101
+ broadcastConfig?: {
102
+ /**
103
+ * Receiver of the broadcast notifications:
104
+ * none - skips sending
105
+ * users - sends to all users in backstage, might have performance impact
106
+ * config - sends to the emails specified in the config
107
+ */
108
+ receiver: 'none' | 'users' | 'config';
109
+ /**
110
+ * Broadcast notification receivers when receiver is set to config
111
+ */
112
+ receiverEmails?: string[];
113
+ };
114
+ cache?: {
115
+ /**
116
+ * Email cache TTL, defaults to 1 hour
117
+ */
118
+ ttl?: HumanDuration;
119
+ };
120
+ };
121
+ };
122
+ };
123
+ }
@@ -0,0 +1,309 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var backendPluginApi = require('@backstage/backend-plugin-api');
6
+ var catalogClient = require('@backstage/catalog-client');
7
+ var pluginNotificationsNode = require('@backstage/plugin-notifications-node');
8
+ var config = require('@backstage/config');
9
+ var types = require('@backstage/types');
10
+ var nodemailer = require('nodemailer');
11
+ var clientSes = require('@aws-sdk/client-ses');
12
+ var lodash = require('lodash');
13
+ var integrationAwsNode = require('@backstage/integration-aws-node');
14
+ var pThrottle = require('p-throttle');
15
+
16
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
17
+
18
+ var pThrottle__default = /*#__PURE__*/_interopDefaultCompat(pThrottle);
19
+
20
+ const createSmtpTransport = (config) => {
21
+ var _a, _b;
22
+ const username = config.getOptionalString("username");
23
+ const password = config.getOptionalString("password");
24
+ return nodemailer.createTransport({
25
+ host: config.getString("hostname"),
26
+ port: config.getNumber("port"),
27
+ secure: (_a = config.getOptionalBoolean("secure")) != null ? _a : false,
28
+ requireTLS: (_b = config.getOptionalBoolean("requireTls")) != null ? _b : false,
29
+ auth: username && password ? { user: username, pass: password } : void 0
30
+ });
31
+ };
32
+
33
+ const createSesTransport = async (config, credentialsManager) => {
34
+ var _a;
35
+ const credentials = await credentialsManager.getCredentialProvider({
36
+ accountId: config.getOptionalString("accountId")
37
+ });
38
+ const ses = new clientSes.SES([
39
+ {
40
+ apiVersion: (_a = config.getOptionalString("apiVersion")) != null ? _a : "2010-12-01",
41
+ credentials: credentials.sdkCredentialProvider,
42
+ region: config.getOptionalString("region")
43
+ }
44
+ ]);
45
+ return nodemailer.createTransport({
46
+ SES: { ses, aws: { SendRawEmailCommand: clientSes.SendRawEmailCommand } }
47
+ });
48
+ };
49
+
50
+ const createSendmailTransport = (config) => {
51
+ var _a, _b;
52
+ return nodemailer.createTransport({
53
+ sendmail: true,
54
+ newline: (_a = config.getOptionalString("newline")) != null ? _a : "unix",
55
+ path: (_b = config.getOptionalString("path")) != null ? _b : "/usr/sbin/sendmail"
56
+ });
57
+ };
58
+
59
+ var __defProp = Object.defineProperty;
60
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
61
+ var __publicField = (obj, key, value) => {
62
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
63
+ return value;
64
+ };
65
+ class NotificationsEmailProcessor {
66
+ constructor(logger, config$1, catalog, auth, cache, templateRenderer) {
67
+ this.logger = logger;
68
+ this.config = config$1;
69
+ this.catalog = catalog;
70
+ this.auth = auth;
71
+ this.cache = cache;
72
+ this.templateRenderer = templateRenderer;
73
+ __publicField(this, "transporter");
74
+ __publicField(this, "broadcastConfig");
75
+ __publicField(this, "transportConfig");
76
+ __publicField(this, "sender");
77
+ __publicField(this, "replyTo");
78
+ __publicField(this, "cacheTtl");
79
+ __publicField(this, "concurrencyLimit");
80
+ __publicField(this, "throttleInterval");
81
+ var _a;
82
+ const emailProcessorConfig = config$1.getConfig(
83
+ "notifications.processors.email"
84
+ );
85
+ this.transportConfig = emailProcessorConfig.getConfig("transport");
86
+ this.broadcastConfig = emailProcessorConfig.getOptionalConfig("broadcastConfig");
87
+ this.sender = emailProcessorConfig.getString("sender");
88
+ this.replyTo = emailProcessorConfig.getOptionalString("replyTo");
89
+ this.concurrencyLimit = (_a = emailProcessorConfig.getOptionalNumber("concurrencyLimit")) != null ? _a : 2;
90
+ const throttleConfig = emailProcessorConfig.getOptionalConfig("throttleInterval");
91
+ this.throttleInterval = throttleConfig ? types.durationToMilliseconds(config.readDurationFromConfig(throttleConfig)) : 100;
92
+ const cacheConfig = emailProcessorConfig.getOptionalConfig("cache.ttl");
93
+ this.cacheTtl = cacheConfig ? types.durationToMilliseconds(config.readDurationFromConfig(cacheConfig)) : 36e5;
94
+ }
95
+ async getTransporter() {
96
+ if (this.transporter) {
97
+ return this.transporter;
98
+ }
99
+ const transport = this.transportConfig.getString("transport");
100
+ if (transport === "smtp") {
101
+ this.transporter = createSmtpTransport(this.transportConfig);
102
+ } else if (transport === "ses") {
103
+ const awsCredentialsManager = integrationAwsNode.DefaultAwsCredentialsManager.fromConfig(
104
+ this.config
105
+ );
106
+ this.transporter = await createSesTransport(
107
+ this.transportConfig,
108
+ awsCredentialsManager
109
+ );
110
+ } else if (transport === "sendmail") {
111
+ this.transporter = createSendmailTransport(this.transportConfig);
112
+ } else {
113
+ throw new Error(`Unsupported transport: ${transport}`);
114
+ }
115
+ return this.transporter;
116
+ }
117
+ getName() {
118
+ return "Email";
119
+ }
120
+ async getBroadcastEmails() {
121
+ var _a, _b, _c;
122
+ if (!this.broadcastConfig) {
123
+ return [];
124
+ }
125
+ const receiver = this.broadcastConfig.getString("receiver");
126
+ if (receiver === "none") {
127
+ return [];
128
+ }
129
+ if (receiver === "config") {
130
+ return (_a = this.broadcastConfig.getOptionalStringArray("receiverEmails")) != null ? _a : [];
131
+ }
132
+ if (receiver === "users") {
133
+ const cached = await ((_b = this.cache) == null ? void 0 : _b.get("user-emails:all"));
134
+ if (cached) {
135
+ return cached;
136
+ }
137
+ const { token } = await this.auth.getPluginRequestToken({
138
+ onBehalfOf: await this.auth.getOwnServiceCredentials(),
139
+ targetPluginId: "catalog"
140
+ });
141
+ const entities = await this.catalog.getEntities(
142
+ {
143
+ filter: [
144
+ { kind: "user", "spec.profile.email": catalogClient.CATALOG_FILTER_EXISTS }
145
+ ],
146
+ fields: ["spec.profile.email"]
147
+ },
148
+ { token }
149
+ );
150
+ const ret = lodash.compact([
151
+ ...new Set(
152
+ entities.items.map((entity) => {
153
+ var _a2;
154
+ return (_a2 = entity == null ? void 0 : entity.spec.profile) == null ? void 0 : _a2.email;
155
+ })
156
+ )
157
+ ]);
158
+ await ((_c = this.cache) == null ? void 0 : _c.set("user-emails:all", ret, {
159
+ ttl: this.cacheTtl
160
+ }));
161
+ return ret;
162
+ }
163
+ throw new Error(`Unsupported broadcast receiver: ${receiver}`);
164
+ }
165
+ async getUserEmail(entityRef) {
166
+ var _a, _b, _c;
167
+ const cached = await ((_a = this.cache) == null ? void 0 : _a.get(`user-emails:${entityRef}`));
168
+ if (cached) {
169
+ return cached;
170
+ }
171
+ const { token } = await this.auth.getPluginRequestToken({
172
+ onBehalfOf: await this.auth.getOwnServiceCredentials(),
173
+ targetPluginId: "catalog"
174
+ });
175
+ const entity = await this.catalog.getEntityByRef(entityRef, { token });
176
+ const ret = [];
177
+ if (entity) {
178
+ const userEntity = entity;
179
+ if ((_b = userEntity.spec.profile) == null ? void 0 : _b.email) {
180
+ ret.push(userEntity.spec.profile.email);
181
+ }
182
+ }
183
+ await ((_c = this.cache) == null ? void 0 : _c.set(`user-emails:${entityRef}`, ret, {
184
+ ttl: this.cacheTtl
185
+ }));
186
+ return ret;
187
+ }
188
+ async getRecipientEmails(notification, options) {
189
+ if (options.recipients.type === "broadcast" || notification.user === null) {
190
+ return await this.getBroadcastEmails();
191
+ }
192
+ return await this.getUserEmail(notification.user);
193
+ }
194
+ async sendMail(options) {
195
+ try {
196
+ await this.transporter.sendMail(options);
197
+ } catch (e) {
198
+ this.logger.error(`Failed to send email to ${options.to}: ${e}`);
199
+ }
200
+ }
201
+ async sendMails(options, emails) {
202
+ const throttle = pThrottle__default.default({
203
+ limit: this.concurrencyLimit,
204
+ interval: this.throttleInterval
205
+ });
206
+ const throttled = throttle((opts) => this.sendMail(opts));
207
+ await Promise.all(
208
+ emails.map((email) => throttled({ ...options, to: email }))
209
+ );
210
+ }
211
+ async sendPlainEmail(notification, emails) {
212
+ const contentParts = [];
213
+ if (notification.payload.description) {
214
+ contentParts.push(`${notification.payload.description}`);
215
+ }
216
+ if (notification.payload.link) {
217
+ contentParts.push(`${notification.payload.link}`);
218
+ }
219
+ const mailOptions = {
220
+ from: this.sender,
221
+ subject: notification.payload.title,
222
+ html: `<p>${contentParts.join("<br/>")}</p>`,
223
+ text: contentParts.join("\n\n"),
224
+ replyTo: this.replyTo
225
+ };
226
+ await this.sendMails(mailOptions, emails);
227
+ }
228
+ async sendTemplateEmail(notification, emails) {
229
+ var _a, _b, _c, _d, _e, _f, _g;
230
+ const mailOptions = {
231
+ from: this.sender,
232
+ subject: (_c = (_b = (_a = this.templateRenderer) == null ? void 0 : _a.getSubject) == null ? void 0 : _b.call(_a, notification)) != null ? _c : notification.payload.title,
233
+ html: (_e = (_d = this.templateRenderer) == null ? void 0 : _d.getHtml) == null ? void 0 : _e.call(_d, notification),
234
+ text: (_g = (_f = this.templateRenderer) == null ? void 0 : _f.getText) == null ? void 0 : _g.call(_f, notification),
235
+ replyTo: this.replyTo
236
+ };
237
+ await this.sendMails(mailOptions, emails);
238
+ }
239
+ async postProcess(notification, options) {
240
+ this.transporter = await this.getTransporter();
241
+ let emails = [];
242
+ try {
243
+ emails = await this.getRecipientEmails(notification, options);
244
+ } catch (e) {
245
+ this.logger.error(`Failed to resolve recipient emails: ${e}`);
246
+ return;
247
+ }
248
+ if (emails.length === 0) {
249
+ this.logger.info(
250
+ `No email recipients found for notification: ${notification.id}, skipping`
251
+ );
252
+ return;
253
+ }
254
+ if (!this.templateRenderer) {
255
+ await this.sendPlainEmail(notification, emails);
256
+ return;
257
+ }
258
+ await this.sendTemplateEmail(notification, emails);
259
+ }
260
+ }
261
+
262
+ const notificationsEmailTemplateExtensionPoint = backendPluginApi.createExtensionPoint({
263
+ id: "notifications.email.templates"
264
+ });
265
+
266
+ const notificationsModuleEmail = backendPluginApi.createBackendModule({
267
+ pluginId: "notifications",
268
+ moduleId: "email",
269
+ register(reg) {
270
+ let templateRenderer;
271
+ reg.registerExtensionPoint(notificationsEmailTemplateExtensionPoint, {
272
+ setTemplateRenderer(renderer) {
273
+ if (templateRenderer) {
274
+ throw new Error(`Email template renderer was already registered`);
275
+ }
276
+ templateRenderer = renderer;
277
+ }
278
+ });
279
+ reg.registerInit({
280
+ deps: {
281
+ config: backendPluginApi.coreServices.rootConfig,
282
+ notifications: pluginNotificationsNode.notificationsProcessingExtensionPoint,
283
+ discovery: backendPluginApi.coreServices.discovery,
284
+ logger: backendPluginApi.coreServices.logger,
285
+ auth: backendPluginApi.coreServices.auth,
286
+ cache: backendPluginApi.coreServices.cache
287
+ },
288
+ async init({ config, notifications, discovery, logger, auth, cache }) {
289
+ const catalogClient$1 = new catalogClient.CatalogClient({
290
+ discoveryApi: discovery
291
+ });
292
+ notifications.addProcessor(
293
+ new NotificationsEmailProcessor(
294
+ logger,
295
+ config,
296
+ catalogClient$1,
297
+ auth,
298
+ cache,
299
+ templateRenderer
300
+ )
301
+ );
302
+ }
303
+ });
304
+ }
305
+ });
306
+
307
+ exports.default = notificationsModuleEmail;
308
+ exports.notificationsEmailTemplateExtensionPoint = notificationsEmailTemplateExtensionPoint;
309
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/processor/transports/smtp.ts","../src/processor/transports/ses.ts","../src/processor/transports/sendmail.ts","../src/processor/NotificationsEmailProcessor.ts","../src/extensions.ts","../src/module.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createTransport } from 'nodemailer';\nimport { Config } from '@backstage/config';\n\nexport const createSmtpTransport = (config: Config) => {\n const username = config.getOptionalString('username');\n const password = config.getOptionalString('password');\n\n return createTransport({\n host: config.getString('hostname'),\n port: config.getNumber('port'),\n secure: config.getOptionalBoolean('secure') ?? false,\n requireTLS: config.getOptionalBoolean('requireTls') ?? false,\n auth: username && password ? { user: username, pass: password } : undefined,\n });\n};\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createTransport } from 'nodemailer';\nimport { SendRawEmailCommand, SES } from '@aws-sdk/client-ses';\nimport { Config } from '@backstage/config';\nimport { AwsCredentialsManager } from '@backstage/integration-aws-node';\n\nexport const createSesTransport = async (\n config: Config,\n credentialsManager: AwsCredentialsManager,\n) => {\n const credentials = await credentialsManager.getCredentialProvider({\n accountId: config.getOptionalString('accountId'),\n });\n const ses = new SES([\n {\n apiVersion: config.getOptionalString('apiVersion') ?? '2010-12-01',\n credentials: credentials.sdkCredentialProvider,\n region: config.getOptionalString('region'),\n },\n ]);\n return createTransport({\n SES: { ses, aws: { SendRawEmailCommand } },\n });\n};\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createTransport } from 'nodemailer';\nimport { Config } from '@backstage/config';\n\nexport const createSendmailTransport = (config: Config) => {\n return createTransport({\n sendmail: true,\n newline: config.getOptionalString('newline') ?? 'unix',\n path: config.getOptionalString('path') ?? '/usr/sbin/sendmail',\n });\n};\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n NotificationProcessor,\n NotificationSendOptions,\n} from '@backstage/plugin-notifications-node';\nimport {\n AuthService,\n CacheService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport { Config, readDurationFromConfig } from '@backstage/config';\nimport { durationToMilliseconds } from '@backstage/types';\nimport {\n CATALOG_FILTER_EXISTS,\n CatalogClient,\n} from '@backstage/catalog-client';\nimport { Notification } from '@backstage/plugin-notifications-common';\nimport {\n createSendmailTransport,\n createSesTransport,\n createSmtpTransport,\n} from './transports';\nimport { UserEntity } from '@backstage/catalog-model';\nimport { compact } from 'lodash';\nimport { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node';\nimport { NotificationTemplateRenderer } from '../extensions';\nimport Mail from 'nodemailer/lib/mailer';\nimport pThrottle from 'p-throttle';\n\nexport class NotificationsEmailProcessor implements NotificationProcessor {\n private transporter: any;\n private readonly broadcastConfig?: Config;\n private readonly transportConfig: Config;\n private readonly sender: string;\n private readonly replyTo?: string;\n private readonly cacheTtl: number;\n private readonly concurrencyLimit: number;\n private readonly throttleInterval: number;\n\n constructor(\n private readonly logger: LoggerService,\n private readonly config: Config,\n private readonly catalog: CatalogClient,\n private readonly auth: AuthService,\n private readonly cache?: CacheService,\n private readonly templateRenderer?: NotificationTemplateRenderer,\n ) {\n const emailProcessorConfig = config.getConfig(\n 'notifications.processors.email',\n );\n this.transportConfig = emailProcessorConfig.getConfig('transport');\n this.broadcastConfig =\n emailProcessorConfig.getOptionalConfig('broadcastConfig');\n this.sender = emailProcessorConfig.getString('sender');\n this.replyTo = emailProcessorConfig.getOptionalString('replyTo');\n this.concurrencyLimit =\n emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2;\n const throttleConfig =\n emailProcessorConfig.getOptionalConfig('throttleInterval');\n this.throttleInterval = throttleConfig\n ? durationToMilliseconds(readDurationFromConfig(throttleConfig))\n : 100;\n const cacheConfig = emailProcessorConfig.getOptionalConfig('cache.ttl');\n this.cacheTtl = cacheConfig\n ? durationToMilliseconds(readDurationFromConfig(cacheConfig))\n : 3_600_000;\n }\n\n private async getTransporter() {\n if (this.transporter) {\n return this.transporter;\n }\n const transport = this.transportConfig.getString('transport');\n if (transport === 'smtp') {\n this.transporter = createSmtpTransport(this.transportConfig);\n } else if (transport === 'ses') {\n const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig(\n this.config,\n );\n this.transporter = await createSesTransport(\n this.transportConfig,\n awsCredentialsManager,\n );\n } else if (transport === 'sendmail') {\n this.transporter = createSendmailTransport(this.transportConfig);\n } else {\n throw new Error(`Unsupported transport: ${transport}`);\n }\n return this.transporter;\n }\n\n getName(): string {\n return 'Email';\n }\n\n private async getBroadcastEmails(): Promise<string[]> {\n if (!this.broadcastConfig) {\n return [];\n }\n\n const receiver = this.broadcastConfig.getString('receiver');\n if (receiver === 'none') {\n return [];\n }\n\n if (receiver === 'config') {\n return (\n this.broadcastConfig.getOptionalStringArray('receiverEmails') ?? []\n );\n }\n\n if (receiver === 'users') {\n const cached = await this.cache?.get<string[]>('user-emails:all');\n if (cached) {\n return cached;\n }\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: await this.auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entities = await this.catalog.getEntities(\n {\n filter: [\n { kind: 'user', 'spec.profile.email': CATALOG_FILTER_EXISTS },\n ],\n fields: ['spec.profile.email'],\n },\n { token },\n );\n const ret = compact([\n ...new Set(\n entities.items.map(entity => {\n return (entity as UserEntity)?.spec.profile?.email;\n }),\n ),\n ]);\n\n await this.cache?.set('user-emails:all', ret, {\n ttl: this.cacheTtl,\n });\n return ret;\n }\n\n throw new Error(`Unsupported broadcast receiver: ${receiver}`);\n }\n\n private async getUserEmail(entityRef: string): Promise<string[]> {\n const cached = await this.cache?.get<string[]>(`user-emails:${entityRef}`);\n if (cached) {\n return cached;\n }\n\n const { token } = await this.auth.getPluginRequestToken({\n onBehalfOf: await this.auth.getOwnServiceCredentials(),\n targetPluginId: 'catalog',\n });\n const entity = await this.catalog.getEntityByRef(entityRef, { token });\n const ret: string[] = [];\n if (entity) {\n const userEntity = entity as UserEntity;\n if (userEntity.spec.profile?.email) {\n ret.push(userEntity.spec.profile.email);\n }\n }\n\n await this.cache?.set(`user-emails:${entityRef}`, ret, {\n ttl: this.cacheTtl,\n });\n\n return ret;\n }\n\n private async getRecipientEmails(\n notification: Notification,\n options: NotificationSendOptions,\n ) {\n if (options.recipients.type === 'broadcast' || notification.user === null) {\n return await this.getBroadcastEmails();\n }\n return await this.getUserEmail(notification.user);\n }\n\n private async sendMail(options: Mail.Options) {\n try {\n await this.transporter.sendMail(options);\n } catch (e) {\n this.logger.error(`Failed to send email to ${options.to}: ${e}`);\n }\n }\n\n private async sendMails(options: Mail.Options, emails: string[]) {\n const throttle = pThrottle({\n limit: this.concurrencyLimit,\n interval: this.throttleInterval,\n });\n\n const throttled = throttle((opts: Mail.Options) => this.sendMail(opts));\n await Promise.all(\n emails.map(email => throttled({ ...options, to: email })),\n );\n }\n\n private async sendPlainEmail(notification: Notification, emails: string[]) {\n const contentParts: string[] = [];\n if (notification.payload.description) {\n contentParts.push(`${notification.payload.description}`);\n }\n if (notification.payload.link) {\n contentParts.push(`${notification.payload.link}`);\n }\n\n const mailOptions = {\n from: this.sender,\n subject: notification.payload.title,\n html: `<p>${contentParts.join('<br/>')}</p>`,\n text: contentParts.join('\\n\\n'),\n replyTo: this.replyTo,\n };\n\n await this.sendMails(mailOptions, emails);\n }\n\n private async sendTemplateEmail(\n notification: Notification,\n emails: string[],\n ) {\n const mailOptions = {\n from: this.sender,\n subject:\n this.templateRenderer?.getSubject?.(notification) ??\n notification.payload.title,\n html: this.templateRenderer?.getHtml?.(notification),\n text: this.templateRenderer?.getText?.(notification),\n replyTo: this.replyTo,\n };\n\n await this.sendMails(mailOptions, emails);\n }\n\n async postProcess(\n notification: Notification,\n options: NotificationSendOptions,\n ): Promise<void> {\n this.transporter = await this.getTransporter();\n\n let emails: string[] = [];\n try {\n emails = await this.getRecipientEmails(notification, options);\n } catch (e) {\n this.logger.error(`Failed to resolve recipient emails: ${e}`);\n return;\n }\n\n if (emails.length === 0) {\n this.logger.info(\n `No email recipients found for notification: ${notification.id}, skipping`,\n );\n return;\n }\n\n if (!this.templateRenderer) {\n await this.sendPlainEmail(notification, emails);\n return;\n }\n\n await this.sendTemplateEmail(notification, emails);\n }\n}\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createExtensionPoint } from '@backstage/backend-plugin-api';\nimport { Notification } from '@backstage/plugin-notifications-common';\n\n/**\n * @public\n */\nexport interface NotificationTemplateRenderer {\n getSubject?(notification: Notification): string;\n getText?(notification: Notification): string;\n getHtml?(notification: Notification): string;\n}\n\n/**\n * @public\n */\nexport interface NotificationsEmailTemplateExtensionPoint {\n setTemplateRenderer(renderer: NotificationTemplateRenderer): void;\n}\n\n/**\n * @public\n */\nexport const notificationsEmailTemplateExtensionPoint =\n createExtensionPoint<NotificationsEmailTemplateExtensionPoint>({\n id: 'notifications.email.templates',\n });\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n coreServices,\n createBackendModule,\n} from '@backstage/backend-plugin-api';\nimport { CatalogClient } from '@backstage/catalog-client';\nimport { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node';\nimport { NotificationsEmailProcessor } from './processor';\nimport {\n notificationsEmailTemplateExtensionPoint,\n NotificationTemplateRenderer,\n} from './extensions';\n\n/**\n * @public\n */\nexport const notificationsModuleEmail = createBackendModule({\n pluginId: 'notifications',\n moduleId: 'email',\n register(reg) {\n let templateRenderer: NotificationTemplateRenderer | undefined;\n reg.registerExtensionPoint(notificationsEmailTemplateExtensionPoint, {\n setTemplateRenderer(renderer) {\n if (templateRenderer) {\n throw new Error(`Email template renderer was already registered`);\n }\n templateRenderer = renderer;\n },\n });\n\n reg.registerInit({\n deps: {\n config: coreServices.rootConfig,\n notifications: notificationsProcessingExtensionPoint,\n discovery: coreServices.discovery,\n logger: coreServices.logger,\n auth: coreServices.auth,\n cache: coreServices.cache,\n },\n async init({ config, notifications, discovery, logger, auth, cache }) {\n const catalogClient = new CatalogClient({\n discoveryApi: discovery,\n });\n\n notifications.addProcessor(\n new NotificationsEmailProcessor(\n logger,\n config,\n catalogClient,\n auth,\n cache,\n templateRenderer,\n ),\n );\n },\n });\n },\n});\n"],"names":["createTransport","SES","SendRawEmailCommand","config","durationToMilliseconds","readDurationFromConfig","DefaultAwsCredentialsManager","CATALOG_FILTER_EXISTS","compact","_a","pThrottle","createExtensionPoint","createBackendModule","coreServices","notificationsProcessingExtensionPoint","catalogClient","CatalogClient"],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import * as _backstage_backend_plugin_api from '@backstage/backend-plugin-api';
2
+ import { Notification } from '@backstage/plugin-notifications-common';
3
+
4
+ /**
5
+ * @public
6
+ */
7
+ declare const notificationsModuleEmail: () => _backstage_backend_plugin_api.BackendFeature;
8
+
9
+ /**
10
+ * @public
11
+ */
12
+ interface NotificationTemplateRenderer {
13
+ getSubject?(notification: Notification): string;
14
+ getText?(notification: Notification): string;
15
+ getHtml?(notification: Notification): string;
16
+ }
17
+ /**
18
+ * @public
19
+ */
20
+ interface NotificationsEmailTemplateExtensionPoint {
21
+ setTemplateRenderer(renderer: NotificationTemplateRenderer): void;
22
+ }
23
+ /**
24
+ * @public
25
+ */
26
+ declare const notificationsEmailTemplateExtensionPoint: _backstage_backend_plugin_api.ExtensionPoint<NotificationsEmailTemplateExtensionPoint>;
27
+
28
+ export { type NotificationTemplateRenderer, type NotificationsEmailTemplateExtensionPoint, notificationsModuleEmail as default, notificationsEmailTemplateExtensionPoint };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@backstage/plugin-notifications-backend-module-email",
3
+ "version": "0.0.0-nightly-20240426021211",
4
+ "description": "The email backend module for the notifications plugin.",
5
+ "backstage": {
6
+ "role": "backend-plugin-module"
7
+ },
8
+ "publishConfig": {
9
+ "access": "public",
10
+ "main": "dist/index.cjs.js",
11
+ "types": "dist/index.d.ts"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/backstage/backstage",
16
+ "directory": "plugins/notifications-backend-module-email"
17
+ },
18
+ "license": "Apache-2.0",
19
+ "main": "dist/index.cjs.js",
20
+ "types": "dist/index.d.ts",
21
+ "files": [
22
+ "dist",
23
+ "config.d.ts"
24
+ ],
25
+ "scripts": {
26
+ "build": "backstage-cli package build",
27
+ "clean": "backstage-cli package clean",
28
+ "lint": "backstage-cli package lint",
29
+ "prepack": "backstage-cli package prepack",
30
+ "postpack": "backstage-cli package postpack",
31
+ "start": "backstage-cli package start",
32
+ "test": "backstage-cli package test"
33
+ },
34
+ "dependencies": {
35
+ "@aws-sdk/client-ses": "^3.550.0",
36
+ "@aws-sdk/types": "^3.347.0",
37
+ "@backstage/backend-common": "^0.0.0-nightly-20240426021211",
38
+ "@backstage/backend-plugin-api": "^0.0.0-nightly-20240426021211",
39
+ "@backstage/catalog-client": "^0.0.0-nightly-20240426021211",
40
+ "@backstage/catalog-model": "^0.0.0-nightly-20240426021211",
41
+ "@backstage/config": "^1.2.0",
42
+ "@backstage/integration-aws-node": "^0.1.12",
43
+ "@backstage/plugin-notifications-common": "^0.0.3",
44
+ "@backstage/plugin-notifications-node": "^0.0.0-nightly-20240426021211",
45
+ "@backstage/types": "^1.1.1",
46
+ "lodash": "^4.17.21",
47
+ "nodemailer": "^6.9.13",
48
+ "p-throttle": "^6.1.0"
49
+ },
50
+ "devDependencies": {
51
+ "@backstage/backend-test-utils": "^0.0.0-nightly-20240426021211",
52
+ "@backstage/cli": "^0.0.0-nightly-20240426021211",
53
+ "@types/nodemailer": "^6.4.14"
54
+ },
55
+ "configSchema": "config.d.ts"
56
+ }