Notification-System
Das Notification-System ist ein zentral registriertes, mehrkanaliges Benachrichtigungs-Framework. Eine einzige Registry definiert alle Notification-Typen; Auslieferung, Kanäle und Sichtbarkeit werden über zwei Einstellungs-Ebenen gesteuert (global/admin und pro Benutzer). Vorlagen sind mehrsprachig und pro Kanal getrennt.
Verbindungs-/Setup-Details der externen Kanäle (E-Mail-Mailboxen, Microsoft Teams Bot Framework, Cisco Webex Bot, Webhooks) stehen unter Integrations. Diese Seite beschreibt, wie Benachrichtigungen modelliert, konfiguriert und ausgeliefert werden.
Architektur
NOTIFICATION_REGISTRY (zentrale Definition aller Typen)
175 TypeKeys: entityType, event, category, title, themeColor + NOTIFICATION_CONFIG: defaultChannels, allowedChannels, isCritical,
isEnforced, digestEligible, customerFacing,
accountLifecycle
|
v (Seed: eine Zeile pro TypeKey, admin-Edits bleiben erhalten)
notification_type_configs (DB) ← GLOBAL/ADMIN-Ebene |
v
Zustellung (Dispatch)
* Effektive Kanäle auflösen (Global ∩ User-Override) * Kontostatus (nicht aktiv → nur accountLifecycle-Typen) * Quiet-Hours-Filter (außer IN_APP / isCritical) * customerFacing-Prüfung (portallose Kontakte) * Template in der Sprache des Empfängers rendern * Übergabe an den notification-worker |
v
notification-worker
| | | | |
v v v v v
IN_APP EMAIL PUSH TEAMS WEBEX
(WebSocket) (Worker) (Web Push) (Bot Fwk) (Bot API)
^
Channel-Setup siehe Integrations
Kanäle
NotificationChannel = IN_APP | EMAIL | PUSH | WEBEX | TEAMS
| Channel | Transport | Beschreibung |
|---|---|---|
IN_APP | WebNotification (DB + WebSocket) | Echtzeit im Portal (Bell-Icon). Umgeht Quiet Hours immer. |
EMAIL | SMTP / MS Graph (email-worker) | E-Mail mit Signatur & Layout. Markdown → HTML. |
PUSH | Web Push API (Service Worker) | Browser-Push, Multi-Device via VAPID |
TEAMS | Microsoft Bot Framework | Adaptive Cards, DM + Channel (Setup: Integrations) |
WEBEX | Cisco Webex Bot API | Direct Messages, native Markdown (Setup: Integrations) |
Notification-Typen & Registry
Jeder Notification-Typ ist genau einmal in der Registry definiert. Daraus wird pro Typ eine Konfigurationszeile (notification_type_configs) erzeugt. Die 14 Kategorien:
tickets, problems, changes, incidents,
workflows, users, assets, contracts, licenses,
system, absences, sla, inventory, reports
Attribute pro Typ
| Attribut | Bedeutung |
|---|---|
defaultChannels | Standard-Kanäle (vom User überschreibbar, außer enforced) |
allowedChannels | Kanäle, die der User aktivieren darf |
isCritical | Umgeht Quiet Hours (z.B. SLA_BREACH, CHANGE_APPROVAL_REQUIRED) |
isEnforced | User kann nicht abwählen (erzwungene Zustellung) |
digestEligible | Darf in Digest gebündelt werden (Default true) |
customerFacing | Wird auch an portallose / E-Mail-only-Kontakte zugestellt |
accountLifecycle | Erreicht auch gesperrte und archivierte Konten — ausschließlich per E-Mail (siehe unten) |
Beispiel-Definitionen (verkürzt) aus der Registry:
// defaultChannels: [IN_APP, EMAIL]; allowedChannels: all 5
TICKET_ASSIGNED: { defaultChannels: CH_IA_EM, allowedChannels: CH_ALL, customerFacing: true }
TICKET_COMMENT_ADDED: { defaultChannels: [IN_APP], allowedChannels: [IN_APP,EMAIL,PUSH], customerFacing: true }
// Critical → bypasses Quiet Hours
SLA_BREACH: { defaultChannels: CH_IA_EM, allowedChannels: CH_ALL, isCritical: true }
CHANGE_APPROVAL_REQUIRED: { defaultChannels: CH_IA_EM, allowedChannels: CH_ALL, isCritical: true }
// Email only, customer-facing (e.g. invitation/reset to portal-less users)
USER_INVITATION: { defaultChannels: [EMAIL], allowedChannels: [EMAIL], digestEligible: false, customerFacing: true }
USER_PASSWORD_RESET: { defaultChannels: [EMAIL], allowedChannels: [EMAIL], isCritical: true, customerFacing: true }
// Account lifecycle — reaches an account that is no longer active, email only
USER_ARCHIVED: { defaultChannels: [EMAIL], allowedChannels: [EMAIL], digestEligible: false, accountLifecycle: true }
USER_AUTO_CREATED_PRIVACY_NOTICE: { defaultChannels: [EMAIL], allowedChannels: [EMAIL], customerFacing: true, accountLifecycle: true }
// Reopen / Lifecycle — ticket variants customer-facing (with redaction), Incident/Problem internal
TICKET_REOPENED: { defaultChannels: CH_IA_EM, allowedChannels: CH_ALL, customerFacing: true }
TICKET_AUTO_CLOSE_WARNING: { defaultChannels: CH_IA_EM, allowedChannels: CH_ALL, customerFacing: true }
TICKET_WC_RESOLVE_WARNING: { defaultChannels: CH_IA_EM, allowedChannels: CH_ALL, customerFacing: true }
REOPEN_ESCALATION: { defaultChannels: CH_IA_EM, allowedChannels: CH_ALL, isCritical: true } // internal
// {TICKET,PROBLEM,INCIDENT}_STALE_REMINDER + {PROBLEM,INCIDENT}_REOPENED: internal (CH_IA_EM)
// Sub-tickets — internal: to the parent ticket's assignee, never to the customer
TICKET_CHILD_RESOLVED: { defaultChannels: CH_IA_EM, allowedChannels: CH_ALL }
TICKET_CHILD_REOPENED: { defaultChannels: CH_IA_EM, allowedChannels: CH_ALL }
customerFacing (kundenseitig): Kontakte ohne Portal-Zugang (kein Passwort, emailOnlyContact, autoCreatedFromEmail) erhalten standardmäßig keine E-Mail-Benachrichtigungen. Typen mit customerFacing=true werden auch an sie zugestellt — so erreichen z.B. Ticket-Updates oder Einladungen auch reine E-Mail-Kontakte.
Sub-Ticket-Meldungen am Elternticket: TICKET_CHILD_RESOLVED (ein Sub-Ticket ist fertig) und TICKET_CHILD_REOPENED (ein Sub-Ticket ist wieder offen) gehen an den Bearbeiter des Elterntickets; ist keiner gesetzt, an dessen zuständige Gruppe. Beteiligte erreichen sie nur mit dem Recht tickets.viewInternal. Der Kunde des Elterntickets ist nie Empfänger — für ihn existiert das Sub-Ticket nicht. Beide Meldungen nennen die Nummer des Sub-Tickets und die Zahl der noch offenen Sub-Tickets.
Standardkanäle sind Glocke und E-Mail; erlaubt sind alle fünf Kanäle. Vorlagen liegen für IN_APP, EMAIL, WEBEX und TEAMS bereit.
Gesperrte und archivierte Konten
Ein Konto, das nicht aktiv ist (gesperrt oder archiviert), erhält keine Benachrichtigungen — auch keine kundenseitigen. Es kann sich weder anmelden noch seine Einstellungen bedienen, deshalb greifen hier weder Nutzer-Einstellungen noch Ruhezeiten noch der Digest. Der Audit-Trail führt die Unterdrückung mit dem Grund account_locked bzw. account_archived; nach außen sind beide Fälle gleich, im Audit bleiben sie unterscheidbar. Offene Digest-Puffer eines solchen Kontos werden beim nächsten Lauf verworfen — es geht keine Sammelmail mehr hinaus.
Ausnahme Konto-Lebenszyklus (accountLifecycle): Ein Schalter je Benachrichtigungstyp (Admin → Benachrichtigungen → Typen) erlaubt einem Typ, ein nicht aktives Konto doch zu erreichen — ausschließlich per E-Mail, ohne Nutzer-Einstellungen, ohne Ruhezeiten, ohne Digest. Ein global abgeschalteter Typ (isEnabled=false) sendet auch dann nichts. Zwei Typen tragen den Schalter: die Archivierungs-Nachricht (USER_ARCHIVED) und die Art.-14-Information an auto-erstellte E-Mail-Kontakte (USER_AUTO_CREATED_PRIVACY_NOTICE). Typen, die zum Anmelden auffordern (Willkommen, Einladung), tragen ihn bewusst nicht — ein gesperrtes Konto käme dem Aufruf nicht nach.
Deshalb ist die Archivierungs-Nachricht eine E-Mail und kein Eintrag in der Glocke: ein archiviertes Konto erreicht die Anwendung nicht mehr und hätte den Eintrag nie gesehen.
Einstellungs-Ebene 1: Global / Admin
Administratoren steuern pro Typ, ob er aktiv ist, welche Kanäle erlaubt/Standard sind und ob er erzwungen wird. Permission: notifications.editGlobalSettings.
/api/admin/notification-types
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | / | Alle Typ-Konfigurationen |
GET | /stats | Statistik (aktiv/enforced/...) |
GET | /categories | Kategorien |
GET | /category/:category | Typen einer Kategorie |
GET | /:typeKey | Einzelne Typ-Konfiguration |
PATCH | /:typeKey | Konfiguration ändern (Channels, Flags) |
PUT | /bulk | Mehrere Typen gleichzeitig |
POST | /:typeKey/enable | Typ aktivieren |
POST | /:typeKey/disable | Typ deaktivieren (global aus) |
POST | /:typeKey/enforce | Erzwungen schalten (User-Override aus) |
NotificationTypeConfig
model NotificationTypeConfig {
typeKey String @unique // "TICKET_ASSIGNED", "SLA_BREACH", ...
category String
isEnabled Boolean @default(true)
isEnforced Boolean @default(false) // user cannot override
isCritical Boolean @default(false) // bypasses quiet hours
defaultChannels NotificationChannel[] @default([IN_APP])
allowedChannels NotificationChannel[] @default([IN_APP, EMAIL, PUSH, WEBEX, TEAMS])
digestEligible Boolean @default(true)
customerFacing Boolean @default(false)
accountLifecycle Boolean @default(false) // reaches non-active accounts (EMAIL only)
}
Einstellungs-Ebene 2: Pro Benutzer
Jeder Benutzer verwaltet seine eigenen Präferenzen: Sprache, Zeitzone, Sound/Browser-Push, Digest, Quiet Hours pro Kanal und pro-Typ Kanal-Overrides. Auth: eigener Account.
/api/web-notifications
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | / | In-App-Notifications, seitenweise über ?cursor=&limit= (Standard 20, max. 50). Antwort: {data, nextCursor, hasMore}. |
GET | /unread | Ungelesene samt Gesamtzahl: {data, count}. ?limit= (Standard 10, max. 50). |
PATCH | /:id/read | Als gelesen markieren → 204 |
POST | /read-all | Alle als gelesen → 204 |
DELETE | /:id | Notification löschen → 204 |
GET | /preferences | Sound-/Mute-Präferenz lesen: {soundEnabled} |
PATCH | /preferences | Sound-/Mute-Präferenz setzen: {soundEnabled} |
- Die Inbox gehört dem Aufrufer: alle Endpoints verlangen einen angemeldeten Benutzer (API-Keys werden abgewiesen) und arbeiten ausschließlich auf dessen eigenen Zeilen. Eine fremde ID ist deshalb von einer erfundenen nicht zu unterscheiden — beide antworten 404.
- Die drei Mutationen antworten 204 ohne Body und sind wiederholbar: dieselbe Zeile ein zweites Mal als gelesen zu markieren oder zu löschen ist erneut 204. Den neuen Ungelesen-Zähler meldet die Live-Verbindung, nicht die Antwort — die Glocke zählt dadurch in allen offenen Fenstern gleichzeitig.
- Der cursor ist ein undurchsichtiger Wert aus der vorigen Antwort (nextCursor); ein selbst gebauter oder abgeschnittener Cursor wird mit 400 INVALID_CURSOR abgewiesen.
Wo die Einstellungen liegen: Die vollständigen Per-User-Einstellungen (Sprache, Zeitzone, Quiet Hours, Digest, Kanal-Overrides pro Typ) liegen unter /api/users/:userId/notification-preferences. /api/web-notifications/preferences bedient nur die Sound-/Mute-Präferenz.
UserNotificationSettings
model UserNotificationSettings {
userId String @unique
soundEnabled Boolean @default(true)
browserPushEnabled Boolean @default(false)
preferredLanguage String @default("de")
timezone String @default("Europe/Berlin")
// Digest
digestEnabled Boolean @default(false)
digestFrequency String? // "HOURLY" | "DAILY" | "WEEKLY"
digestTime String? // "08:00"
digestDayOfWeek Int? // 0=Sun … 6=Sat
// Quiet Hours per channel (JSON):
// { "EMAIL": { enabled, startTime:"22:00", endTime:"07:00", days:["MON",...] }, ... }
quietHoursConfig Json @default("{}")
typeSettings UserNotificationTypeSetting[] // per-type overrides
}
model UserNotificationTypeSetting {
typeKey String
isEnabled Boolean @default(true)
// hasChannelOverride=false → use global defaults
// true + channelsOverride=[] → no channels
// true + channelsOverride=[...] → exactly these channels
hasChannelOverride Boolean @default(false)
channelsOverride NotificationChannel[] @default([])
}
Auflösung der effektiven Kanäle
1. Konto nicht aktiv (gesperrt/archiviert)? → nichts senden; nur ein Typ mit accountLifecycle geht raus — dann EMAIL, ohne Nutzer-Einstellungen, Ruhezeiten und Digest2. Typ global deaktiviert (isEnabled=false)? → nichts senden3. isEnforced=true? → defaultChannels erzwingen (User-Override ignoriert)4. sonst: defaultChannels, gefiltert durch User-Override (∩ allowedChannels)5. Quiet-Hours-Filter pro Kanal: - IN_APP → immer durch - isCritical → immer durch - sonst innerhalb Quiet Hours → unterdrückt (ggf. Digest)6. customerFacing-Guard für portallose Empfänger7. Template-Render pro Kanal: kein aktives Template (isActive=false) oder kein gerendertes HTML → Kanal wird übersprungen (EMAIL fällt auf IN_APP zurück), kein Leerversand
In-App-Notifications
IN_APP-Notifications werden als WebNotification gespeichert und in Echtzeit per WebSocket an das Portal gepusht (Bell-Icon). Sie werden nie durch Quiet Hours unterdrückt. Endpoints siehe Tabelle oben (/api/web-notifications).
Dieselbe Verbindung hält auch Listen, Detailseiten und Betrachter-Avatare aktuell: Echtzeit & Presence →
Web-Push
Browser-Push über die Web Push API (VAPID). Ein Benutzer kann auf mehreren Geräten subscriben; jede Subscription ist an die Login-Session gekoppelt (Logout auf einem Gerät entfernt nur dessen Subscription).
/api/push
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | /vapid-public-key | Öffentlicher VAPID-Key (ohne Auth) |
GET | /status?endpoint= | Status für DIESES Gerät: {serverEnabled, masterEnabled, deviceSubscribed, subscriptionCount} |
GET | /subscriptions | Geräte auflisten: {data} |
POST | /subscribe | Gerät registrieren (rate-limited): {subscriptionId, deviceName, isNew} |
PATCH | /subscriptions/:id | Geräte-Bezeichnung ändern (deviceLabel) → 204 |
DELETE | /subscriptions/current | Aktuelles Gerät abmelden → 204 |
DELETE | /subscriptions/:id | Bestimmtes Gerät abmelden → 204 |
DELETE | /subscriptions | Alle Geräte abmelden → 204 |
- Der öffentliche VAPID-Key ist der einzige Endpoint dieser beiden Flächen ohne Anmeldung — der Browser braucht ihn, bevor er sich registrieren kann. Alle übrigen verlangen einen angemeldeten Benutzer und weisen API-Keys mit 403 ab.
- Ein Benutzer kann bis zu 20 Geräte registrieren; der Versuch, ein 21. anzumelden, wird mit 409 MAX_PUSH_SUBSCRIPTIONS_REACHED abgewiesen (details nennt currentCount und limit). Eine erneute Registrierung desselben Geräts zählt nicht mit.
- Die Registrierung ist an die Anmelde-Sitzung gekoppelt; ohne Sitzungsbezug antwortet /subscribe 401 NO_SESSION_ID. Ist auf dem Server kein VAPID-Schlüsselpaar hinterlegt, antworten /vapid-public-key und /subscribe 503 PUSH_NOT_CONFIGURED.
Fehlercodes
| errorCode | HTTP | Bedeutung |
|---|---|---|
NOTIFICATION_NOT_FOUND | 404 | Die Zeile gehört nicht zum Aufrufer oder existiert nicht. Eine bereits gelesene oder gelöschte eigene Zeile ist dagegen 204. |
INVALID_CURSOR | 400 | Der Seiten-Cursor ist nicht lesbar. Nur nextCursor aus der vorigen Antwort verwenden. |
VALIDATION_ERROR | 400 | Ein Query- oder Body-Wert passt nicht zum Schema (limit außerhalb 1–50, unbekanntes Feld in den Präferenzen, ungültiger Endpoint beim Registrieren). |
SUBSCRIPTION_NOT_FOUND | 404 | Die Geräte-Registrierung gehört nicht zum Aufrufer oder existiert nicht. |
MAX_PUSH_SUBSCRIPTIONS_REACHED | 409 | Die Obergrenze von 20 Geräten je Benutzer ist erreicht. |
NO_SESSION_ID | 401 | Registrieren und Abmelden des aktuellen Geräts brauchen den Sitzungsbezug des Anmelde-Tokens. |
PUSH_NOT_CONFIGURED | 503 | Auf dem Server ist kein VAPID-Schlüsselpaar hinterlegt — Push ist serverseitig aus. |
Sprache und Datumsform einer Benachrichtigung
Eine Push-Nachricht ist die einzige Darstellung, die der Server fertig formulieren muss — das Gerät zeigt sie auch bei geschlossener Anwendung. Sie kommt deshalb in der Sprache des Empfängers, und zwar aus derselben Quelle, aus der die Glocke im Browser ihren Text zieht: bevorzugt aus dem hinterlegten Textbaustein der Zeile, sonst aus dem aktiven IN_APP-Template des Typs, sonst aus dem gespeicherten Text mit übersetzter Typ-Überschrift. Ein Wiederholungsversuch nach einem fehlgeschlagenen Zustellversuch trägt denselben Wortlaut wie der Erstversuch.
Sprache, Zeitzone und Datumsform werden je Empfänger in dieser Reihenfolge aufgelöst — die erste gesetzte Stufe gewinnt:
| Merkmal | Kette |
|---|---|
| Sprache | Benachrichtigungs-Einstellung (preferredLanguage) → Profilsprache → Systemsprache (general-settings.defaultLanguage) → Englisch |
| Zeitzone | Benachrichtigungs-Zeitzone → Profil-Zeitzone → general-settings.timezone → Europe/Berlin |
| Datumsform | Profil-Einstellung (dateTimeFormat) → general-settings.dateTimeFormat → dd/MM/yyyy HH:mm |
Damit folgt jeder server-erzeugte Datumswert derselben Einstellung wie die Oberfläche — in E-Mail, Push, Webex und Teams. Ein Datum in einer Benachrichtigung sieht deshalb genauso aus wie dasselbe Datum in der Anwendung. Ein Zeitpunkt mit Uhrzeit erscheint mit Uhrzeit, eine Frist auf Mitternacht als reines Datum.
Templates (mehrsprachig)
Vorlagen sind pro Typ + Kanal eindeutig und enthalten die Inhalte je Sprache als separate i18n-Einträge — unterstützt sind Deutsch, Englisch, Französisch, Spanisch und Italienisch. Variablen werden gegen ein Schema validiert; Preview/Render erlauben Test ohne Versand. Permission: notifications.manageTemplates.
/api/notification-templates/v2
| Method | Endpoint | Beschreibung |
|---|---|---|
GET | / | Templates auflisten |
GET | /grouped | Nach Typ/Kanal gruppiert |
GET | /meta | Metadaten (Typen, Kanäle) |
GET | /statistics | Abdeckung/Statistik |
GET | /variables/:typeKey | Verfügbare Variablen eines Typs |
GET | /sample-data/:typeKey | Beispieldaten für Preview |
GET | /languages | Unterstützte Sprachen |
POST | /test | Test-Notification senden |
GET | /:id | Einzelnes Template |
GET | /:id/missing-translations | Fehlende Übersetzungen |
POST | / | Template erstellen |
POST | /preview | Vorschau (ohne Speichern) |
POST | /render | Render mit Variablen |
PATCH | /:id | Template aktualisieren |
PUT | /:id/translations | Übersetzung anlegen/ändern |
DELETE | /:id/translations/:languageCode | Übersetzung löschen |
POST | /:id/clone | Template klonen |
DELETE | /:id | Template löschen |
model NotificationTemplateV2 {
typeKey String // "TICKET_ASSIGNED"
channel NotificationChannel
isActive Boolean @default(true)
editorType String @default("MARKDOWN") // "RICH_TEXT" | "MARKDOWN"
version Int @default(1)
variables String[] // schema for validation
i18n NotificationTemplateI18n[]
@@unique([typeKey, channel])
}
model NotificationTemplateI18n {
languageCode String // 'de' | 'en' | 'fr' | 'es' | 'it'
subject String? // EMAIL only
body String
@@unique([templateId, languageCode])
}
Variablen & Rendering
// Template (body):
"**Ticket {{ticketNumber}}** assigned to {{assigneeName}}"
// Render with:
{ ticketNumber: "TK-000123", assigneeName: "John Doe" }
// Result: "**Ticket TK-000123** assigned to John Doe"
// EMAIL channel: Markdown → HTML; auto variables like {{ticketUrl}} added.
Quiet Hours & Digest
Quiet Hours unterdrücken Kanäle während definierter Ruhezeiten — pro Kanal und in der Zeitzone des Benutzers. IN_APP und isCritical-Typen passieren immer. E-Mails digestfähiger Typen (digestEligible), die in die Quiet Hours fallen, werden in den Digest übernommen und mit der nächsten Sammelmail zugestellt.
| quietHoursConfig | Beschreibung |
|---|---|
enabled | Quiet Hours für diesen Kanal aktiv |
startTime / endTime | "22:00" / "07:00" (Mitternachts-Übergang unterstützt) |
days | MON, TUE, WED, THU, FRI, SAT, SUN |
Digest: Der Nutzer wählt in UserNotificationSettings den E-Mail-Zustellmodus Sofort (Default) / Stündlich / Täglich / Wöchentlich — digestEnabled + digestFrequency ("HOURLY"/"DAILY"/"WEEKLY") + digestTime + digestDayOfWeek. Ein 15-Minuten-Cron (notification-digest-dispatch, digest_dispatch-Action) versendet je fälligem Nutzer EINE Sammelmail (NOTIFICATION_DIGEST) in dessen Zeitzone; nach erfolgreichem Versand werden die gepufferten Items gelöscht. Rahmen, Kategorie-Überschriften und Zeilen der Sammelmail stehen durchgängig in der Sprache des Empfängers, und die Zeitangabe je Zeile folgt seiner Datumsform — in derselben Zeitzone, in der auch die Fälligkeit gerechnet wird.
Immer sofort (nie gebündelt): isCritical- und isEnforced-Typen, IN_APP und Push sowie E-Mail-only-/Portal-lose Kontakte umgehen den Digest und werden unmittelbar zugestellt. Bulk-Aktionen (Massenänderungen) werden pro Empfänger zu EINER Sammelmail zusammengefasst, auch ohne aktiven Digest. Administratoren steuern das Feature global über das Setting notification-digest (Hauptschalter, eigener Schalter für die Bulk-Bündelung, Obergrenze der Zeilen je Mail) und pro Typ über den digestEligible-Schalter — GET/PUT /api/admin/notification-types/digest-settings.
Admin-Broadcast
Administratoren können eine Broadcast-Notification an Zielgruppen (User/Team/Rolle/Abteilung/Custom-Group) senden. Permission: settings.sendBroadcast.
POST /api/admin/notifications/broadcast
{
"broadcastId": "550e8400-e29b-41d4-a716-446655440000",
"title": "Wartungsfenster Samstag 08:00–10:00",
"message": "Das System ist während der Wartung nicht erreichbar.",
"severity": "WARN",
"targetRoleIds": ["clx-role-agent"],
"expiresAt": "2026-08-24T10:00:00Z"
}
- Antwort: {created, skipped, broadcastId} (201). severity ∈ INFO | WARN | CRITICAL; targetRoleIds leer oder weggelassen = alle Benutzer.
- broadcastId ist der Idempotenz-Schlüssel: Ein erneuter Aufruf mit derselben ID erzeugt keine zweite Benachrichtigung und keinen zweiten Push — die Antwort meldet dann created: 0 und die übersprungene Menge.
- expiresAt ist optional; ohne Angabe läuft eine Broadcast-Nachricht nach 30 Tagen ab und wird vom Retention-Lauf entfernt.
- Empfänger sind ausschließlich aktive Konten: gesperrte, archivierte und anonymisierte Konten bleiben außen vor — dieselbe Regel wie bei jeder anderen Zustellung.
- Ein von Hand getippter Titel und Text wird wörtlich zugestellt — er trägt keine Übersetzungsbausteine und erscheint bei allen Empfängern gleich, unabhängig von deren Sprache.
- settings.sendBroadcast ist eine kritische Aktion: Das Recht wird frisch aus der Datenbank geprüft, und jeder Versand landet als BROADCAST_SENT im Audit-Trail — ebenso abgelehnte Versuche.
Teams & Webex als Kanal
TEAMS liefert Adaptive Cards (DM oder Channel) über das Microsoft Bot Framework; WEBEX liefert native Markdown-Direktnachrichten über die Webex Bot API. Die themeColor jedes Typs steuert die Kartenfarbe (Blau 0078D4, Grün 107C10, Gelb FFB900, Rot D13438). Die Verbindungs-/Bot-Einrichtung (Azure App, Bot-Token, SSRF-Allowlist) ist im Integrations-Kapitel dokumentiert.
Kalender-Einladungen (.ics)
Geplante Change-Tasks verschicken Kalender-Einladungen (.ics, METHOD:REQUEST) an Zuständige, sofern der Change scheduledStartTime/scheduledEndTime hat und in einem der Status SCHEDULED, APPROVED oder IN_PROGRESS ist. Bei Neuzuweisung/Storno geht ein CANCEL. Details siehe Changes API.
- ✓ Eine Registry, alle Typen abgeleitet
- ✓ Global ∩ User → effektive Kanäle
- ✓ IN_APP & isCritical umgehen Quiet Hours
- ✓ customerFacing erreicht portallose Kontakte
- ✓ Nicht aktive Konten erhalten keine Zustellung
- ✓ Mehrsprachige Templates pro Typ + Kanal
notifications.editGlobalSettings– Globale Typ-Konfigurationnotifications.manageTemplates– Templates verwaltensettings.sendBroadcast– Broadcast senden- Eigene Präferenzen/Push: eingeloggter User
Auth-/Rollenmodell: Permissions & RBAC
- Integrations – E-Mail, Mailboxen, Teams/Webex-Setup, Webhooks, Follower/CC
- Changes API – .ics-Kalender für Change-Tasks
- SLA System – SLA-Warnungen/-Breach-Notifications
- Reopen & Lifecycle – *_REOPENED, TICKET_AUTO_CLOSE_WARNING, REOPEN_ESCALATION