Notifications & Events

Purpose

The Notifications module handles fan-out, retries, and external delivery (webhooks and email) with failure semantics that are fully isolated from the control plane. A delivery failure — webhook timeout, SMTP bounce, dead endpoint — must never propagate back to the file commit path or any synchronous user operation.

Data owned

Table Purpose
subscriptions Tenant webhook endpoint registrations, event filter config
webhook_endpoints Endpoint URL, HMAC signing secret, status
notifications Per-user in-app notification records
delivery_state Per-event × per-endpoint delivery attempts, next retry time, status

Internal API

Notify.* gRPC methods:

Method Description
Notify.Subscribe Register a webhook endpoint for one or more event types
Notify.Unsubscribe Remove a subscription
Notify.ListSubscriptions List a tenant’s active subscriptions
Notify.GetDeliveryStatus Query delivery status for a given event + endpoint

Event consumption and fan-out

The notifier worker subscribes to all domain event subjects on NATS JetStream. For each consumed event it fans out to every matching subscription for the originating tenant:

flowchart LR
    J{{"NATS JetStream<br/>(all domain subjects)"}}
    NT["Notifier worker"]
    WH[("Tenant webhook endpoints")]
    EM[("SMTP / email")]
    IN[("In-app notifications DB")]

    J -->|at-least-once| NT
    NT -->|signed HTTP POST| WH
    NT -->|SMTP| EM
    NT -->|insert| IN

Delivery guarantees

Webhook security

Email notifications

SMTP is used for user-facing notifications:

Email delivery uses the same at-least-once retry model as webhooks.

:::note Failure isolation: Notification delivery failures must never propagate back to the control plane. The async plane is isolated from the control plane by design — the event bus and the notifier worker are the boundary. A broken webhook endpoint or a full SMTP queue is a notifier concern, not a file-commit concern. :::