Eviworx
Docs

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.

🔔
Funktionen
✓ Zentrale Registry mit 175 Typen (14 Kategorien)
✓ 5 Kanäle (IN_APP, EMAIL, PUSH, WEBEX, TEAMS)
✓ Zwei Einstellungs-Ebenen (global + pro Benutzer)
✓ Erreicht auch portallose Kontakte (customerFacing)
✓ Kritische Typen umgehen Quiet Hours (isCritical)
✓ Erzwungene Typen nicht abwählbar (isEnforced)
✓ Templates pro Typ + Kanal in 5 Sprachen
✓ Quiet Hours pro Kanal (Zeitzone des Benutzers)
✓ Optionaler E-Mail-Digest (HOURLY/DAILY/WEEKLY)
✓ Kalender-Einladungen für Change-Tasks (.ics)

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_APPWebNotification (DB + WebSocket)Echtzeit im Portal (Bell-Icon). Umgeht Quiet Hours immer.
EMAILSMTP / MS Graph (email-worker)E-Mail mit Signatur & Layout. Markdown → HTML.
PUSHWeb Push API (Service Worker)Browser-Push, Multi-Device via VAPID
TEAMSMicrosoft Bot FrameworkAdaptive Cards, DM + Channel (Setup: Integrations)
WEBEXCisco Webex Bot APIDirect 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
defaultChannelsStandard-Kanäle (vom User überschreibbar, außer enforced)
allowedChannelsKanäle, die der User aktivieren darf
isCriticalUmgeht Quiet Hours (z.B. SLA_BREACH, CHANGE_APPROVAL_REQUIRED)
isEnforcedUser kann nicht abwählen (erzwungene Zustellung)
digestEligibleDarf in Digest gebündelt werden (Default true)
customerFacingWird auch an portallose / E-Mail-only-Kontakte zugestellt
accountLifecycleErreicht 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
MethodEndpointBeschreibung
GET/Alle Typ-Konfigurationen
GET/statsStatistik (aktiv/enforced/...)
GET/categoriesKategorien
GET/category/:categoryTypen einer Kategorie
GET/:typeKeyEinzelne Typ-Konfiguration
PATCH/:typeKeyKonfiguration ändern (Channels, Flags)
PUT/bulkMehrere Typen gleichzeitig
POST/:typeKey/enableTyp aktivieren
POST/:typeKey/disableTyp deaktivieren (global aus)
POST/:typeKey/enforceErzwungen 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
MethodEndpointBeschreibung
GET/In-App-Notifications, seitenweise über ?cursor=&limit= (Standard 20, max. 50). Antwort: {data, nextCursor, hasMore}.
GET/unreadUngelesene samt Gesamtzahl: {data, count}. ?limit= (Standard 10, max. 50).
PATCH/:id/readAls gelesen markieren → 204
POST/read-allAlle als gelesen → 204
DELETE/:idNotification löschen → 204
GET/preferencesSound-/Mute-Präferenz lesen: {soundEnabled}
PATCH/preferencesSound-/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
MethodEndpointBeschreibung
GET/vapid-public-keyÖffentlicher VAPID-Key (ohne Auth)
GET/status?endpoint=Status für DIESES Gerät: {serverEnabled, masterEnabled, deviceSubscribed, subscriptionCount}
GET/subscriptionsGeräte auflisten: {data}
POST/subscribeGerät registrieren (rate-limited): {subscriptionId, deviceName, isNew}
PATCH/subscriptions/:idGeräte-Bezeichnung ändern (deviceLabel) → 204
DELETE/subscriptions/currentAktuelles Gerät abmelden → 204
DELETE/subscriptions/:idBestimmtes Gerät abmelden → 204
DELETE/subscriptionsAlle 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

errorCodeHTTPBedeutung
NOTIFICATION_NOT_FOUND404Die Zeile gehört nicht zum Aufrufer oder existiert nicht. Eine bereits gelesene oder gelöschte eigene Zeile ist dagegen 204.
INVALID_CURSOR400Der Seiten-Cursor ist nicht lesbar. Nur nextCursor aus der vorigen Antwort verwenden.
VALIDATION_ERROR400Ein 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_FOUND404Die Geräte-Registrierung gehört nicht zum Aufrufer oder existiert nicht.
MAX_PUSH_SUBSCRIPTIONS_REACHED409Die Obergrenze von 20 Geräten je Benutzer ist erreicht.
NO_SESSION_ID401Registrieren und Abmelden des aktuellen Geräts brauchen den Sitzungsbezug des Anmelde-Tokens.
PUSH_NOT_CONFIGURED503Auf 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:

MerkmalKette
SpracheBenachrichtigungs-Einstellung (preferredLanguage) → Profilsprache → Systemsprache (general-settings.defaultLanguage) → Englisch
ZeitzoneBenachrichtigungs-Zeitzone → Profil-Zeitzone → general-settings.timezone → Europe/Berlin
DatumsformProfil-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
MethodEndpointBeschreibung
GET/Templates auflisten
GET/groupedNach Typ/Kanal gruppiert
GET/metaMetadaten (Typen, Kanäle)
GET/statisticsAbdeckung/Statistik
GET/variables/:typeKeyVerfügbare Variablen eines Typs
GET/sample-data/:typeKeyBeispieldaten für Preview
GET/languagesUnterstützte Sprachen
POST/testTest-Notification senden
GET/:idEinzelnes Template
GET/:id/missing-translationsFehlende Übersetzungen
POST/Template erstellen
POST/previewVorschau (ohne Speichern)
POST/renderRender mit Variablen
PATCH/:idTemplate aktualisieren
PUT/:id/translationsÜbersetzung anlegen/ändern
DELETE/:id/translations/:languageCodeÜbersetzung löschen
POST/:id/cloneTemplate klonen
DELETE/:idTemplate 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.

quietHoursConfigBeschreibung
enabledQuiet Hours für diesen Kanal aktiv
startTime / endTime"22:00" / "07:00" (Mitternachts-Übergang unterstützt)
daysMON, 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.

🔔
Kernprinzipien
  • ✓ 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
🔐
Berechtigungen (RBAC)
  • notifications.editGlobalSettings – Globale Typ-Konfiguration
  • notifications.manageTemplates – Templates verwalten
  • settings.sendBroadcast – Broadcast senden
  • Eigene Präferenzen/Push: eingeloggter User

Auth-/Rollenmodell: Permissions & RBAC

Verwandte Dokumentation
  • 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