# Notifications SDK

<Lead>
**`platform.notifications`** ist der In-App-Posteingang der Plattform. Tools nutzen ihn, um Ergebnisse zu melden, auf Probleme hinzuweisen und Nutzer:innen per Action-Button direkt an die richtige Stelle zu bringen.
</Lead>

## Senden an eine Person

Der Service ist eine Factory (`createNotificationsService(db)`); Apps halten einen gecachten Handle. Für Standard-Läufe brauchst du ihn nicht: die Fertig- und Fehler-Notification schickt der Runs-Harness automatisch. Selbst senden ist für alles darüber hinaus:

```typescript
import { createNotificationsService } from "@reosa/sdk";

const notifications = createNotificationsService(db);

const id = await notifications.send(ctx, {
  toUserId: ownerId,            // optional, Default: ctx.userId
  source: "tool",               // "platform" | "tool" | "team" | "billing"
  category: "tool_results",     // optional, sonst aus source abgeleitet (siehe unten)
  toolId: "homestaging",        // optional, ordnet die Nachricht deinem Tool zu
  severity: "success",          // "info" | "success" | "warning"
  title: "Deine Bilder sind fertig.",           // Pflicht, max 200 Zeichen
  bodyMd: "3 Varianten liegen im Dateispeicher.", // Markdown-Subset, max 20000
  actions: [                    // max 4 Buttons
    { label: "Ansehen", href: "/tools/homestaging", variant: "primary" },
  ],
});
// id: Routing-ID wie "n_<uuid>", oder null, wenn die Person diese Kategorie abgeschaltet hat
```

Die Regeln der Eingabe (`notificationSendSchema`, Zod-validiert):

- **`title`** 1 bis 200 Zeichen. **`bodyMd`** ist ein Markdown-Subset (Überschriften, fett/kursiv, Links, Listen, Absätze).
- **`actions`**: maximal 4, `label` bis 60 Zeichen, `href` muss ein Pfad (`/...`) oder eine `https://`-URL sein, `variant` optional `primary` oder `ghost`. Action-Buttons sind das Mittel für Deep-Links in dein Tool.
- **`toUserId`** muss ein Mitglied des aktuellen Tenants sein; ohne Angabe geht die Nachricht an die auslösende Person.
- **`category`** ist die Kategorie, unter der die Person die Nachricht unter Konto, Benachrichtigungen abschalten kann: `tool_results`, `credits_low`, `team`, `billing`, `platform` oder `security`. Ohne Angabe folgt sie der Quelle (`tool` wird zu `tool_results`, `team`, `billing` und `platform` bleiben). `credits_low` und `security` haben keine Quelle und werden ausdrücklich genannt. Sicherheitswarnungen lassen sich nicht abschalten.

Die Präferenzen gelten beim Senden: Hat die Empfängerin die Kategorie für In-App abgeschaltet, entsteht keine Zeile, und `send` liefert `null` statt einer Routing-ID. Ein Tool muss damit rechnen und darf daraus keinen Fehler machen. Der E-Mail-Kanal ist noch nicht in Betrieb; die E-Mail-Wahl der Person wird gespeichert und gilt, sobald er live ist.

## Fan-out an Rollen

Für "alle Admins informieren" gibt es `sendToRole`; es liefert die Anzahl der tatsächlich zugestellten Nachrichten zurück. Wer die Kategorie abgeschaltet hat, zählt nicht mit:

```typescript
const count = await notifications.sendToRole(ctx, ["owner", "admin"], {
  source: "tool",
  toolId: "onoffice-sync",
  severity: "warning",
  title: "Speicherplatz voll: Foto-Import angehalten",
  bodyMd: "Der Import wurde gestoppt, bis wieder Platz frei ist.",
  actions: [{ label: "Speicher verwalten", href: "/settings/billing", variant: "primary" }],
});
```

Rollen sind die vier Tenant-Rollen `owner`, `admin`, `member`, `viewer`.

## Lesen und Lese-Status

Für eigene Inbox-Flächen existieren Lese-APIs: `list(ctx, { limit, unreadOnly })` liefert den zusammengeführten Posteingang (eigene Notifications + Plattform-Ankündigungen, neueste zuerst), dazu `get`, `unreadCount`, `markRead(ctx, id)` und `markAllRead(ctx)`. Die Routing-IDs unterscheiden die Quellen: `n_<uuid>` für Notifications, `a_<uuid>` für Ankündigungen.

## PII-Regeln

- **Keine Geheimnisse, keine sensiblen Nutzerdaten** in `title` oder `bodyMd`. Die Nachricht liegt in der Datenbank und erscheint in jeder Inbox-Fläche der Empfänger:in.
- **Verweise statt Inhalte:** schreibe "Dein Export liegt bereit" mit Action-Button, nicht den Inhalt des Exports in den Body.
- Fehler-Notifications nennen den Grund PII-frei ("Der Lauf wurde abgebrochen"), nie rohe Fehlertexte mit Nutzereingaben.

## Weiterführend

Die automatischen Lauf-Notifications und den Rest des Modul-Vertrags beschreibt der [Modul-Guide](/dev/module-guide). Für Ereignisse, die externe Systeme erreichen sollen, nutzt du [Webhooks und Events](/dev/sdk/webhooks-und-events).
