diff --git a/.changeset/gorgeous-zebras-tan.md b/.changeset/gorgeous-zebras-tan.md new file mode 100644 index 0000000000000..7a71d9c72420a --- /dev/null +++ b/.changeset/gorgeous-zebras-tan.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +--- + +Added more examples of the plugin configuration diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index e0389f0adbfa9..2119400fa7ac8 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -381,6 +381,7 @@ sdks seb semlas semver +sendmail serializable Serverless shoutout diff --git a/docs/notifications/index.md b/docs/notifications/index.md index bb8a7dc246ca8..5b5e2bcfa23a0 100644 --- a/docs/notifications/index.md +++ b/docs/notifications/index.md @@ -130,226 +130,6 @@ export default app.createRoot( If the signals plugin is properly configured, it will be automatically discovered by the notifications plugin and used. -## Configuration - -### Notifications Backend - -The Notifications backend plugin provides an API to create notifications, list notifications per logged-in user, and search based on parameters. - -The plugin uses a relational [database](https://backstage.io/docs/getting-started/config/database) for persistence; no specifics are introduced in this context. - -No additional configuration in the app-config is needed, except for optional additional modules for `processors`. - -### Notifications Frontend - -The recipients of notifications have to be entities in the catalog, e.g., of the User or Group kind. - -Otherwise, no specific configuration is needed for the front-end notifications plugin. - -All parametrization is done through component properties, such as the `NotificationsSidebarItem`, which can be used as an active left-side menu item in the front-end. - -![Notifications Page](notificationsPage.png) - -In the `packages/app/src/components/Root/Root.tsx`, tweak the [properties](https://backstage.io/docs/reference/plugin-notifications.notificationssidebaritem) of the `` per specific needs. - -## Use - -New notifications can be sent either by a backend plugin or an external service through the REST API. - -### Backend - -Regardless of technical feasibility, a backend plugin should avoid directly accessing the notifications REST API. -Instead, it should integrate with the `@backstage/plugin-notifications-node` to `send` (create) a new notification. - -The reasons for this approach include the propagation of authorization in the API request and improved maintenance and backward compatibility in the future. - -```ts -import { notificationService } from '@backstage/plugin-notifications-node'; - -export const myPlugin = createBackendPlugin({ - pluginId: 'myPlugin', - register(env) { - env.registerInit({ - deps: { - // ... - notificationService: notificationService, - }, - async init({ config, logger, httpRouter, notificationService }) { - httpRouter.use( - await createRouter({ - // ... - notificationService, - }), - ); - }, - }); - }, -}); -``` - -To emit a new notification: - -```ts -notificationService.send({ - recipients /* of the broadcast or entity type */, - payload /* actual message */, -}); -``` - -Refer the [API documentation](https://github.com/backstage/backstage/blob/master/plugins/notifications-node/report.api.md) for further details. - -### Signals - -The use of signals with notifications is optional but generally enhances user experience and performance. - -When a notification is created, a new signal is emitted to a general-purpose message bus to announce it to subscribed listeners. - -The frontend maintains a persistent connection (WebSocket) to receive these announcements from the notifications channel. -The specific details of the updated or created notification should be retrieved via a request to the notifications API, except for new notifications, where the payload is included in the signal for performance reasons. - -In a frontend plugin, to subscribe for notifications' signals: - -```ts -import { useSignal } from '@backstage/plugin-signals-react'; - -const { lastSignal } = useSignal('notifications'); - -React.useEffect(() => { - /* ... */ -}, [lastSignal, notificationsApi]); -``` - -#### Using signals in your own plugin - -It's possible to use signals in your own plugin to deliver data from the backend to the frontend in near real-time. - -To use signals in your own frontend plugin, you need to add the `useSignal` hook from `@backstage/plugin-signals-react` from `@backstage/plugin-notifications-common` with optional generic type of the signal. - -```ts -// To use the same type of signal in the backend, this should be placed in a shared common package -export type MySignalType = { - user: string; - data: string; - // .... -}; - -const { lastSignal } = useSignal('my-plugin'); - -useEffect(() => { - if (lastSignal) { - // Do something with the signal - } -}, [lastSignal]); -``` - -To send signals from the backend plugin, you must add the `signalsServiceRef` to your plugin or module as a dependency. - -```ts -import { signalsServiceRef } from '@backstage/plugin-signals-node'; -export const myPlugin = createBackendPlugin({ - pluginId: 'my', - register(env) { - env.registerInit({ - deps: { - httpRouter: coreServices.httpRouter, - signals: signalsServiceRef, - }, - async init({ httpRouter, signals }) { - httpRouter.use( - await createRouter({ - signals, - }), - ); - }, - }); - }, -}); -``` - -To send the signal using the service, you can use the `publish` method. - -```ts -signals.publish({ user: 'user', data: 'test' }); -``` - -### Consuming Notifications - -In a front-end plugin, the simplest way to query a notification is by its ID: - -```ts -import { useApi } from '@backstage/core-plugin-api'; -import { notificationsApiRef } from '@backstage/plugin-notifications'; - -const notificationsApi = useApi(notificationsApiRef); - -notificationsApi.getNotification(yourId); - -// or with connection to signals: -notificationsApi.getNotification(lastSignal.notification_id); -``` - -### Extending Notifications via Processors - -The notifications can be extended with `NotificationProcessor`. These processors allow to decorate notifications before they are sent or/and send the notifications to external services. - -Depending on the needs, a processor can modify the content of a notification or route it to different systems like email, Slack, or other services. - -A good example of how to write a processor is the [Email Processor](https://github.com/backstage/backstage/tree/master/plugins/notifications-backend-module-email). - -Start off by creating a notification processor: - -```ts -import { Notification } from '@backstage/plugin-notifications-common'; -import { NotificationProcessor } from '@backstage/plugin-notifications-node'; - -class MyNotificationProcessor implements NotificationProcessor { - // preProcess is called before the notification is saved to database. - // This is a good place to modify the notification before it is saved and sent to the user. - async preProcess(notification: Notification): Promise { - if (notification.origin === 'plugin-my-plugin') { - notification.payload.icon = 'my-icon'; - } - return notification; - } - - // postProcess is called after the notification is saved to database and the signal is emitted. - // This is a good place to send the notification to external services. - async postProcess(notification: Notification): Promise { - nodemailer.sendEmail({ - from: 'backstage', - to: 'user', - subject: notification.payload.title, - text: notification.payload.description, - }); - } -} -``` - -Both of the processing functions are optional, and you can implement only one of them. - -Add the notification processor to the notification system by: - -```ts -import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; -import { Notification } from '@backstage/plugin-notifications-common'; - -export const myPlugin = createBackendPlugin({ - pluginId: 'myPlugin', - register(env) { - env.registerInit({ - deps: { - notifications: notificationsProcessingExtensionPoint, - // ... - }, - async init({ notifications }) { - // ... - notifications.addProcessor(new MyNotificationProcessor()); - }, - }); - }, -}); -``` - ### User-specific notification settings The notifications plugin provides a way for users to manage their notification settings. To enable this, you must @@ -375,19 +155,6 @@ You can customize the origin names shown in the UI by passing an object where th Each notification processor will receive its own column in the settings page, where the user can enable or disable notifications from that processor. -### External Services - -When the emitter of a notification is a Backstage backend plugin, it is mandatory to use the integration via `@backstage/plugin-notifications-node` as described above. - -If the emitter is a service external to Backstage, an HTTP POST request can be issued directly to the API, assuming that authentication is properly configured. -Refer to the [service-to-service auth documentation](https://backstage.io/docs/auth/service-to-service-auth) for more details, focusing on the Static Tokens section for the simplest setup option. - -An example request for creating a broadcast notification might look like: - -```bash -curl -X POST https://[BACKSTAGE_BACKEND]/api/notifications/notifications -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_BASE64_SHARED_KEY_TOKEN" -d '{"recipients":{"type":"broadcast"},"payload": {"title": "Title of broadcast message","link": "http://foo.com/bar","severity": "high","topic": "The topic"}}' -``` - ## Additional info An example of a backend plugin sending notifications can be found in https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-notifications. @@ -395,9 +162,10 @@ An example of a backend plugin sending notifications can be found in https://git Sources of the notifications and signal plugins: - https://github.com/backstage/backstage/blob/master/plugins/notifications - - https://github.com/backstage/backstage/blob/master/plugins/notifications-backend - +- https://github.com/backstage/backstage/blob/master/plugins/notifications-common - https://github.com/backstage/backstage/blob/master/plugins/notifications-node - +- https://github.com/backstage/backstage/blob/master/plugins/signals-backend +- https://github.com/backstage/backstage/blob/master/plugins/signals +- https://github.com/backstage/backstage/blob/master/plugins/signals-node - https://github.com/backstage/backstage/blob/master/plugins/signals-react diff --git a/docs/notifications/processors.md b/docs/notifications/processors.md new file mode 100644 index 0000000000000..abee7c97c3f72 --- /dev/null +++ b/docs/notifications/processors.md @@ -0,0 +1,107 @@ +--- +id: processors +title: Processors +description: How to setup notification processors +--- + +Notifications can be extended with `NotificationProcessor`. These processors allow you to decorate notifications before they are sent and/or send the notifications to external services. + +Depending on your needs, a processor can modify the content of a notification or route it to different systems like email, Slack, or other services. + +A good example of how to write a processor is the [Email Processor](https://github.com/backstage/backstage/tree/master/plugins/notifications-backend-module-email). + +Start off by creating a notification processor: + +```ts +import { Notification } from '@backstage/plugin-notifications-common'; +import { NotificationProcessor } from '@backstage/plugin-notifications-node'; + +class MyNotificationProcessor implements NotificationProcessor { + // preProcess is called before the notification is saved to database. + // This is a good place to modify the notification before it is saved and sent to the user. + async preProcess(notification: Notification): Promise { + if (notification.origin === 'plugin-my-plugin') { + notification.payload.icon = 'my-icon'; + } + return notification; + } + + // postProcess is called after the notification is saved to database and the signal is emitted. + // This is a good place to send the notification to external services. + async postProcess(notification: Notification): Promise { + nodemailer.sendEmail({ + from: 'backstage', + to: 'user', + subject: notification.payload.title, + text: notification.payload.description, + }); + } +} +``` + +Both of the processing functions are optional, and you can just implement one of them. + +Add the notification processor to the notification system by: + +```ts +import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; +import { Notification } from '@backstage/plugin-notifications-common'; + +export const myPlugin = createBackendPlugin({ + pluginId: 'myPlugin', + register(env) { + env.registerInit({ + deps: { + notifications: notificationsProcessingExtensionPoint, + // ... + }, + async init({ notifications }) { + // ... + notifications.addProcessor(new MyNotificationProcessor()); + }, + }); + }, +}); +``` + +## Built-in Processors + +Backstage comes with some processors that can be used immediately. + +### Email Processor + +Email processor is used to send notifications to users using email. To install the email processor, add the `@backstage/plugin-notifications-backend-module-email` package to your backend. + +```bash +yarn workspace backend add @backstage/plugin-notifications-backend-module-email +``` + +Add the email processor to your backend: + +```ts +import { createBackend } from '@backstage/plugin-notifications-backend'; +const backend = createBackend(); +// ... +backend.add(import('@backstage/plugin-notifications-backend-module-email')); +``` + +To configure the email processor, you need to add the following configuration to your `app-config.yaml`: + +```yaml +notifications: + email: + smtp: + host: smtp.example.com + port: 587 + secure: false + username: ${SMTP_USERNAME} + password: ${SMTP_PASSWORD} +``` + +Apart from STMP, the email processor also supports the following transmissions: + +- SES +- sendmail +- stream (only for debugging purposes) + +See more information at https://github.com/backstage/backstage/blob/master/plugins/notifications-backend-module-email/README.md diff --git a/docs/notifications/usage.md b/docs/notifications/usage.md new file mode 100644 index 0000000000000..59fbd83e3b036 --- /dev/null +++ b/docs/notifications/usage.md @@ -0,0 +1,212 @@ +--- +id: usage +title: Usage +description: How to use the notifications and signals +--- + +## Notifications Backend + +The Notifications backend plugin provides an API to create notifications, list notifications per logged-in user, and search based on parameters. + +The plugin uses a relational [database](https://backstage.io/docs/getting-started/config/database) for persistence; no specifics are introduced in this context. + +No additional configuration in the app-config is needed, except for optional additional modules for `processors`. + +## Notifications Frontend + +The recipients of notifications have to be entities in the catalog, e.g., of the User or Group kind. + +Otherwise, no specific configuration is needed for the front-end notifications plugin. + +All parametrization is done through component properties, such as the `NotificationsSidebarItem`, which can be used as an active left-side menu item in the front-end. + +![Notifications Page](notificationsPage.png) + +In the `packages/app/src/components/Root/Root.tsx`, tweak the [properties](https://backstage.io/docs/reference/plugin-notifications.notificationssidebaritem) of the `` per specific needs. + +## Usage + +New notifications can be sent either by a backend plugin or an external service through the REST API. + +## Backend + +Regardless of technical feasibility, a backend plugin should avoid directly accessing the notifications REST API. +Instead, it should integrate with the `@backstage/plugin-notifications-node` to `send` (create) a new notification. + +The reasons for this approach include the propagation of authorization in the API request and improved maintenance and backward compatibility in the future. + +```ts +import { notificationService } from '@backstage/plugin-notifications-node'; + +export const myPlugin = createBackendPlugin({ + pluginId: 'myPlugin', + register(env) { + env.registerInit({ + deps: { + // ... + notificationService: notificationService, + }, + async init({ + // ... + notificationService, + }) { + httpRouter.use( + await createRouter({ + // ... + notificationService, + }), + ); + }, + }); + }, +}); +``` + +To emit a new notification: + +```ts +await notificationService.send({ + recipients /* of the broadcast or entity type */, + payload /* actual message */, +}); +``` + +Refer the [API documentation](https://github.com/backstage/backstage/blob/master/plugins/notifications-node/report.api.md) for further details. + +### External Services + +When the emitter of a notification is a Backstage backend plugin, it is mandatory to use the integration via `@backstage/plugin-notifications-node` as described above. + +If the emitter is a service external to Backstage, an HTTP POST request can be issued directly to the API, assuming that authentication is properly configured. +Refer to the [service-to-service auth documentation](https://backstage.io/docs/auth/service-to-service-auth) for more details, focusing on the Static Tokens section for the simplest setup option. + +An example request for creating a broadcast notification might look like: + +```bash +curl -X POST https://[BACKSTAGE_BACKEND]/api/notifications -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_BASE64_SHARED_KEY_TOKEN" -d '{"recipients":{"type":"broadcast"},"payload": {"title": "Title of broadcast message","link": "http://foo.com/bar","severity": "high","topic": "The topic"}}' +``` + +### Scaffolder Templates + +You can use the `@backstage/plugin-scaffolder-backend-module-notifications` to send notifications when scaffolder templates are run. To install the module, add it to your backend plugin: + +```bash +yarn workspace backend add @backstage/plugin-scaffolder-backend-module-notifications +``` + +Then, add the module to your backend: + +```ts +const backend = createBackend(); +// ... +backend.add( + import('@backstage/plugin-scaffolder-backend-module-notifications'), +); +``` + +In your template you can now use `notification:send` action as part of the steps: + +```yaml +steps: + - id: notify + name: Notify + action: notification:send + input: + recipients: entity + entityRefs: + - component:default/backstage + title: 'Template executed' + info: 'Your template has been executed' + severity: 'info' + link: https://backstage.io +``` + +## Signals + +The use of signals with notifications is optional but generally enhances user experience and performance. + +When a notification is created, a new signal is emitted to a general-purpose message bus to announce it to subscribed listeners. + +The frontend maintains a persistent connection (WebSocket) to receive these announcements from the notifications channel. +The specific details of the updated or created notification should be retrieved via a request to the notifications API, except for new notifications, where the payload is included in the signal for performance reasons. + +In a frontend plugin, to subscribe to notifications' signals: + +```ts +import { useSignal } from '@backstage/plugin-signals-react'; + +const { lastSignal } = useSignal('notifications'); + +React.useEffect(() => { + /* ... */ +}, [lastSignal, notificationsApi]); +``` + +#### Using signals in your own plugin + +It's possible to use signals in your own plugin to deliver data from the backend to the frontend in near real-time. + +To use signals in your own frontend plugin, you need to add the `useSignal` hook from `@backstage/plugin-signals-react` from `@backstage/plugin-notifications-common` with optional generic type of the signal. + +```ts +// To use the same type of signal in the backend, this should be placed in a shared common package +export type MySignalType = { + user: string; + data: string; + // .... +}; + +const { lastSignal } = useSignal('my-plugin'); + +useEffect(() => { + if (lastSignal) { + // Do something with the signal + } +}, [lastSignal]); +``` + +To send signals from the backend plugin, you must add the `signalsServiceRef` to your plugin or module as a dependency. + +```ts +import { signalsServiceRef } from '@backstage/plugin-signals-node'; +export const myPlugin = createBackendPlugin({ + pluginId: 'my', + register(env) { + env.registerInit({ + deps: { + httpRouter: coreServices.httpRouter, + signals: signalsServiceRef, + }, + async init({ httpRouter, signals }) { + httpRouter.use( + await createRouter({ + signals, + }), + ); + }, + }); + }, +}); +``` + +To send the signal using the service, you can use the `publish` method. + +```ts +signals.publish({ user: 'user', data: 'test' }); +``` + +## Consuming Notifications + +In a front-end plugin, the simplest way to query a notification is by its ID: + +```ts +import { useApi } from '@backstage/core-plugin-api'; +import { notificationsApiRef } from '@backstage/plugin-notifications'; + +const notificationsApi = useApi(notificationsApiRef); + +notificationsApi.getNotification(yourId); + +// or with connection to signals: +notificationsApi.getNotification(lastSignal.notification_id); +``` diff --git a/microsite/sidebars.js b/microsite/sidebars.js index e48e3b7b5283e..546a43b8c6bc9 100644 --- a/microsite/sidebars.js +++ b/microsite/sidebars.js @@ -102,7 +102,11 @@ module.exports = { { type: 'category', label: 'Notifications', - items: ['notifications/index'], + items: [ + 'notifications/index', + 'notifications/processors', + 'notifications/usage', + ], }, { type: 'category', diff --git a/mkdocs.yml b/mkdocs.yml index 748cdd8232abe..a7e693e5fcea2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -158,6 +158,10 @@ nav: - Reading Backstage Configuration: 'conf/reading.md' - Writing Backstage Configuration: 'conf/writing.md' - Defining Configuration for your Plugin: 'conf/defining.md' + - Notifications: + - Getting Started: 'notifications/index.md' + - Usage: 'notifications/usage.md' + - Processors: 'notifications/processors.md' - Authentication and identity: - Adding Authentication: 'auth/index.md' - Included providers: diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md index 1defad74b4de7..3ccee8f1e82f3 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -24,13 +24,13 @@ export const notificationsModuleEmailDecorator = createBackendModule({ }, async init({ emailTemplates }) { emailTemplates.setTemplateRenderer({ - getSubject(notification) { + async getSubject(notification) { return `New notification from ${notification.source}`; }, - getText(notification) { + async getText(notification) { return notification.content; }, - getHtml(notification) { + async getHtml(notification) { return `

${notification.content}

`; }, }); @@ -54,19 +54,50 @@ notifications: secure: false username: 'my-username' password: 'my-password' + + # AWS SES + # transportConfig: + # transport: 'ses' + # accessKeyId: 'my-access-key + # region: 'us-west-2' + + # sendmail + # transportConfig: + # transport: 'sendmail' + # path: '/usr/sbin/sendmail' + # newline: 'unix' + # The email sender address sender: 'sender@mycompany.com' replyTo: 'no-reply@mycompany.com' - # Who to get email for broadcast notifications + # Who to send email for broadcast notifications broadcastConfig: receiver: 'users' # How many emails to send concurrently, defaults to 2 concurrencyLimit: 10 + # How much to throttle between emails, defaults to 100ms + throttleInterval: + seconds: 60 # Cache configuration for email addresses # This is to prevent unnecessary calls to the catalog cache: ttl: days: 1 + # Notification filter which this processor will handle + filter: + # Minimum severity of the notification to send email + minSeverity: high + # Maximum severity of the notification to send email + maxSeverity: critical + # Topics that are excluded from sending email + excludedTopics: + - scaffolder + # List of allowed email addresses to get notifications via email + allowlistEmailAddresses: + - john.doe@backstage.io + # List of denied email addresses to get notifications via email + denylistEmailAddresses: + - jane.doe@backstage.io ``` See `config.d.ts` for more options for configuration.