# Notifications Real-time notification infrastructure with WebSocket delivery and optional email transport. **Auth:** All endpoints require authentication (JWT via `sb_token` cookie or `Authorization: Bearer` header). ### CRUD ``` GET /notifications → paginated list GET /notifications/unread-count → { "count": 5 } PATCH /notifications/:id/read → mark one as read POST /notifications/mark-all-read → mark all as read DELETE /notifications/:id ``` **`GET /notifications`** — paginated list for the authenticated user. Query parameters: | Param | Type | Default | Description | |-------|------|---------|-------------| | `limit` | int | 20 | 1–100, clamped | | `offset` | int | 0 | Pagination offset | | `unread_only` | bool | false | Filter to unread only | Response: ```json { "data": [ ... ], "total": 42, "limit": 20, "offset": 0 } ``` Notification object: ```json { "id": "uuid", "user_id": "uuid", "type": "role.fallback", "title": "Role Fallback Triggered", "body": "Primary model unavailable, using fallback", "resource_type": "channel", "resource_id": "uuid", "is_read": false, "created_at": "2025-01-15T12:00:00Z" } ``` `resource_type` and `resource_id` are optional — used for click-to-navigate. The resource may be deleted (no FK constraint). ### Preferences Users control per-type delivery: ``` GET /notifications/preferences → { "data": [...] } PUT /notifications/preferences/:type ← { "in_app": true, "email": false } DELETE /notifications/preferences/:type → reset to default ``` **`GET /notifications/preferences`** returns all preferences set by the user. Empty array if none are configured. **`PUT /notifications/preferences/:type`** creates or updates a preference. Both `in_app` and `email` fields are optional (merges with existing values if set). **`DELETE /notifications/preferences/:type`** removes the preference, falling back to the next level in the resolution chain. Idempotent — deleting a non-existent preference returns 200. Resolution: specific type pref → user wildcard `*` pref → system default (`in_app=true`, `email=false`). Returns 503 if the preference store is not available (unmanaged mode). ### Admin ``` POST /admin/notifications/test-email → send test email to requesting admin ``` Requires admin role. Loads SMTP config from platform settings, sends a test message to the admin's registered email address. ### Admin Broadcast (v0.28.6) ``` POST /admin/notifications/broadcast ``` **Auth:** Admin only. Sends a `system.announcement` notification to all active users via `NotifyMany`. Also emits a `system.broadcast` WebSocket event for real-time visibility. **Request body:** ```json { "title": "Scheduled Maintenance", "message": "Servers will be down from 2-4 AM EST.", "level": "warning" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `title` | string | yes | Short summary shown in bell dropdown | | `message` | string | yes | Detail text | | `level` | string | no | `info` (default), `warning`, or `critical` | **Response:** ```json { "message": "broadcast sent", "count": 42 } ``` `count` is the number of active users notified. ### WebSocket Events | Event | Payload | Description | |-------|---------|-------------| | `notification.new` | Full notification object | New notification created | | `notification.read` | `{"id": "uuid"}` | Single notification marked read | | `notification.read` | `{"action": "mark_all_read"}` | All notifications marked read | | `system.broadcast` | `{"title", "message", "level"}` | Admin announcement (v0.28.6) | These are targeted via `Hub.SendToUser` — no room subscription required. Used for badge sync across tabs. ### Notification Types See [enums.md](enums.md#notification-types) for the canonical list. Types are free-form strings (`domain.action` convention) — new types don't require migration. Core types include `system.announcement` (admin broadcast, v0.28.6). ### Retention Background cleanup prunes notifications older than 90 days (default, configurable via `WithRetention`). Runs daily after a 5-minute startup delay. ---