docs(notification-outbox): central-only Notification Service, typed lists, async API

This commit is contained in:
Joseph Doherty
2026-05-18 23:09:29 -04:00
parent a4ba2dfe01
commit 0f96562bfd

View File

@@ -2,84 +2,95 @@
## Purpose
The Notification Service provides email notification capabilities to scripts running at site clusters. It manages notification lists, handles email delivery, and integrates with the Store-and-Forward Engine for reliable delivery when the email server is unavailable.
The Notification Service is the central component that manages notification-list and SMTP definitions and provides the per-type delivery adapters used to send notifications. It manages notification lists, manages email server configuration, and supplies the stateless "deliver one notification" adapter implementations that the Notification Outbox invokes at delivery time.
The Notification Service no longer delivers notifications from sites. Notification delivery has been inverted: a site script's notification is store-and-forwarded to the central cluster, and the central **Notification Outbox** owns dispatch and delivery, calling an `INotificationDeliveryAdapter` supplied by this component. See [`Component-NotificationOutbox.md`](Component-NotificationOutbox.md).
## Location
Central cluster (definition management, stores in config DB). Site clusters (email delivery, reads definitions from local SQLite).
Central cluster only. The Notification Service manages definitions in the central configuration database and provides the delivery adapters that run on the central cluster. It is no longer present at site clusters, and notification definitions and SMTP configuration are no longer deployed to sites.
## Responsibilities
### Definitions (Central)
- Store notification lists in the configuration database: list name, recipients (name + email address).
- Store notification lists in the configuration database: list name, list **type**, and type-specific targets (e.g. recipients for an `Email` list).
- Store email server configuration (SMTP settings).
- Deploy notification lists and SMTP configuration uniformly to all sites. Deployment requires **explicit action** by a user with the Deployment role.
- Managed by users with the Design role.
- Notification lists and SMTP configuration are **not deployed to sites** — they exist centrally only. There is no deploy-to-sites artifact and no local SQLite copy.
### Delivery (Site)
- Resolve notification list names to recipient lists from **local SQLite** (populated by artifact deployment). Sites do not access the central config DB.
- Compose and send emails via SMTP using locally stored SMTP configuration.
- On delivery failure, submit the notification to the Store-and-Forward Engine for buffered retry.
### Delivery Adapters (Central)
- Provide a delivery adapter implementing `INotificationDeliveryAdapter` for each notification `Type`.
- Each adapter is a stateless "deliver one notification" implementation: it composes and sends a single notification and classifies the outcome.
- The **Email adapter** is the relocated SMTP composition and send logic — formerly run at sites, it now runs on the central cluster.
- Resolve a notification list name to its concrete targets (e.g. recipient addresses) at delivery time, on behalf of the Notification Outbox.
## Notification List Definition
Each notification list includes:
- **Name**: Unique identifier (e.g., "Maintenance-Team", "Shift-Supervisors").
- **Recipients**: One or more entries, each with:
- **Type**: The notification type — `Email` (implemented now); `Teams` and other types are planned for the future. `Notify.To("list")` works transparently for any type — the calling script does not care about the type.
- **Type-specific targets**: The targets appropriate to the list type. For an `Email` list, one or more recipient entries, each with:
- Recipient name.
- Email address.
Lists are defined and stored centrally only. **Recipient resolution happens at central, at delivery time** — a site forwards only `(listName, subject, body)` plus provenance; the Notification Outbox asks the Notification Service to resolve the list when it dispatches the notification.
## Email Server Configuration
The SMTP configuration is defined centrally and deployed to all sites. It includes:
The SMTP configuration is defined centrally and used by the central Email delivery adapter. It is not deployed to sites. It includes:
- **Server hostname**: SMTP server address (e.g., `smtp.office365.com`).
- **Port**: SMTP port (e.g., 587 for StartTLS, 465 for SSL).
- **Authentication mode**: One of:
- **Basic Auth**: Username and password. For on-prem SMTP relays or servers that support basic authentication.
- **OAuth2 Client Credentials**: Tenant ID, Client ID, and Client Secret. For Microsoft 365 and other modern SMTP providers that require OAuth2. The Notification Service handles the token lifecycle internally (fetch, cache, refresh on expiry).
- **OAuth2 Client Credentials**: Tenant ID, Client ID, and Client Secret. For Microsoft 365 and other modern SMTP providers that require OAuth2. The Email adapter handles the token lifecycle internally (fetch, cache, refresh on expiry).
- **TLS mode**: None, StartTLS, or SSL.
- **From address**: The sender email address for all notifications (e.g., `scada-notifications@company.com`).
- **Connection timeout**: Maximum time to wait for SMTP connection (default: 30 seconds).
- **Max concurrent connections**: Maximum simultaneous SMTP connections per site (default: 5).
- **Retry settings**: Max retry count, fixed time between retries (used by Store-and-Forward Engine for transient failures).
- **Max concurrent connections**: Maximum simultaneous SMTP connections from the central cluster (default: 5).
- **Retry settings**: Max retry count, fixed time between retries. The Notification Outbox reuses these for delivery retry of transient failures.
## Script API
```csharp
Notify.To("listName").Send("subject", "message")
NotificationId id = Notify.To("listName").Send("subject", "message");
NotificationStatus status = Notify.Status(id);
```
- Available to instance scripts (via Script Execution Actors), alarm on-trigger scripts (via Alarm Execution Actors), and shared scripts (executing inline).
- Resolves the list name to recipients, composes the email, and attempts delivery.
- `Notify.To("listName").Send(...)` is **asynchronous**: it generates a `NotificationId` (GUID) locally, hands the notification to the site Store-and-Forward Engine for forwarding to central, and returns the `NotificationId` to the script **immediately**. The script does not block waiting for delivery.
- The message body is **plain text** only. No HTML content.
- `Notify.Status(notificationId)` returns a small **status record** — the current status, retry count, last error, and key timestamps (enqueued, delivered). While the notification is still in the site Store-and-Forward buffer, the site answers the query **locally** with status `Forwarding`; once forwarded to central, the query round-trips to central and reads the `Notifications` table.
## Email Delivery Behavior
## Notification Delivery Behavior
Delivery is performed centrally by the Notification Outbox, which calls the `INotificationDeliveryAdapter` registered for the notification's `Type`. The behavior below describes the Email adapter.
### Recipient Handling
- A single email is sent per `Notify.To().Send()` call, with all list recipients in **BCC**. The from address is placed in the To field.
- A single email is sent per notification, with all list recipients in **BCC**. The from address is placed in the To field.
- Recipients do not see each other's email addresses.
- No per-recipient deduplication — if the same email address appears in multiple lists and a script sends to both, they receive multiple emails.
### Error Classification
Consistent with the External System Gateway pattern:
- **Transient failures** (connection refused, timeout, SMTP 4xx temporary errors): The notification is handed to the **Store-and-Forward Engine** for buffered retry per the SMTP configuration's retry settings. The script does **not** block waiting for eventual delivery.
- **Permanent failures** (SMTP 5xx permanent errors, e.g., mailbox not found): The error is returned **synchronously** to the calling script for handling. No retry — the notification will never deliver.
- This prevents the S&F buffer from accumulating notifications that will never succeed.
Each `Deliver(...)` call returns one of `success | transient failure | permanent failure`, consistent with the External System Gateway pattern. There is **no synchronous permanent-failure return to the script**`Send()` returns immediately, before any delivery is attempted.
- **Transient failures** (connection refused, timeout, SMTP 4xx temporary errors): The Notification Outbox moves the row to `Retrying` and schedules another attempt per the SMTP configuration's retry settings.
- **Permanent failures** (SMTP 5xx permanent errors, e.g., mailbox not found): The Notification Outbox moves the row to `Parked` with the error in `LastError`. The notification will never deliver, and an operator can review or discard it on the Central UI Notification Outbox page.
- Retries exhausted on a transient failure also result in a `Parked` row.
- A script observes failures only by calling `Notify.Status(id)` and seeing a `Parked` status — not as a synchronous exception.
### No Rate Limiting
- No application-level rate limiting. If the SMTP server enforces sending limits (e.g., Microsoft 365 throttling), those manifest as transient failures and are handled naturally by store-and-forward.
- No application-level rate limiting. If the SMTP server enforces sending limits (e.g., Microsoft 365 throttling), those manifest as transient failures and are retried naturally by the Notification Outbox.
## Dependencies
- **Configuration Database (MS SQL)**: Stores notification list definitions and SMTP config (central only).
- **Local SQLite**: At sites, notification lists, recipients, and SMTP configuration are read from local SQLite (populated by artifact deployment). Sites do not access the central config DB.
- **Store-and-Forward Engine**: Handles buffering for failed email deliveries.
- **Configuration Database (MS SQL)**: Stores notification list definitions (name, type, type-specific targets) and SMTP config.
- **Notification Outbox**: Invokes the delivery adapters supplied by this component and asks it to resolve notification lists at delivery time.
- **Security & Auth**: Design role manages notification lists.
- **Configuration Database (via IAuditService)**: Notification list changes are audit logged.
## Interactions
- **Site Runtime (Script/Alarm Execution Actors)**: Scripts invoke `Notify.To().Send()` through this component.
- **Store-and-Forward Engine**: Failed notifications are buffered here.
- **Deployment Manager**: Receives updated notification lists and SMTP config as part of system-wide artifact deployment (triggered explicitly by Deployment role).
- **Notification Outbox**: Consumes the per-type delivery adapters and the list-resolution service this component provides; the outbox owns dispatch, retry, parking, and status.
- **Site Runtime (Script/Alarm Execution Actors)**: Scripts invoke `Notify.To().Send()` and `Notify.Status()`. `Send()` generates a `NotificationId` and hands the notification to the site Store-and-Forward Engine; it does not contact this component synchronously.
- **Store-and-Forward Engine (site)**: Forwards a script's notification to central; the central Notification Outbox ingests it for delivery. The Notification Service does not interact with the site Store-and-Forward Engine directly.