# Notifications

## Delivery

Operational notifications are dispatched per organization member. Each user has
two independent channels (`inApp` and `email`) plus an event preference for:

- server offline;
- outdated bridge;
- expiring license;
- failed payment;
- expiring trial;
- expiring API key;
- structurally failing webhook;
- resource error spikes.

Preferences are stored on the user account and therefore apply consistently to
all organizations. Billing notifications are limited to owners and
administrators. In-app records are user-scoped; users cannot read or mark
another user's notification.

Every delivery has a deterministic deduplication key. Re-running a detector,
retrying a job or restarting the worker therefore does not create duplicate
in-app messages or emails.

## Detectors and thresholds

The worker checks notification conditions every five minutes. Server-offline
and license-lifecycle events are emitted directly by their lifecycle jobs.

- A server is offline after the configured heartbeat timeout.
- A bridge is outdated below `BRIDGE_WARN_BELOW_VERSION`.
- Licenses and API keys warn seven days before expiration.
- Trials warn three days before expiration.
- Payment failures notify immediately after a failed renewal and are also
  recovered by the periodic detector.
- A webhook is structurally failing after three consecutive failed deliveries.
- A resource error spike is five or more `resource.error` events for the same
  resource and server within fifteen minutes.

## Email worker and branding

Emails are persisted in `EmailOutbox`. A worker atomically claims messages,
sends at most 25 per run and retries failures with exponential backoff up to
five attempts. Claims older than five minutes are recovered after a worker
crash. Successful bodies are removed after delivery.

Operational templates use the configured product name, primary color, optional
logo, support address and a contextual dashboard action. Plain-text fallbacks
are always included.
