Zum Hauptinhalt springen

Notifications & Mail

Die Notification-Services verwalten das Benachrichtigungssystem von Postbox: In-App-Announcements, E-Mail-Versand, Benutzer-Präferenzen und Flood-Schutz. Die Architektur trennt zwischen per-User Notifications (Laravel notifications Tabelle) und geteilten Announcements (announcements Tabelle mit Read-Tracking).

NotificationService

Location: app/Services/Notifications/NotificationService.php

Zentraler Service für Benachrichtigungen und Announcements. Vereint direkte Notifications und Announcements in einer einheitlichen API, verwaltet Read/Unread-Status und steuert den E-Mail-Versand.

Public Methods

MethodParameterReturnBeschreibung
getForUser()User $user, int $limit = 20CollectionAlle Benachrichtigungen für User (merged + sortiert)
getPaginatedForUser()User $user, int $perPage = 20LengthAwarePaginatorPaginierte Benachrichtigungen
getUnreadCount()User $userintUngelesene Anzahl (gecacht, 5 Min TTL)
calculateUnreadCount()User $userintUngelesene Anzahl (direkt berechnet)
markAsRead()User $user, string $source, string|int $idvoidEinzelne Benachrichtigung als gelesen markieren
markAsUnread()User $user, string $source, string|int $idvoidEinzelne Benachrichtigung als ungelesen markieren
markAllAsRead()User $uservoidAlle Benachrichtigungen als gelesen markieren (nutzt chunkById(5000) für Announcements wegen PostgreSQL 65.535 Parameter-Limit)
delete()User $user, string $source, string|int $idvoidNotification löschen / Announcement dismissen
announceToUser()User $user, string $type, array $data, ?int $expiryDays, ?int $workspaceIdAnnouncementAnnouncement für einzelnen User erstellen
announceToWorkspace()Workspace $workspace, string $type, array $data, ?int $expiryDaysAnnouncementAnnouncement für Workspace erstellen
announceToAll()string $type, array $data, bool $adminOnly, ?int $expiryDaysAnnouncementAnnouncement für alle User/Admins erstellen
invalidateUnreadCountCache()User $uservoidUnread-Count-Cache invalidieren

Announcements vs. Notifications

EigenschaftAnnouncementsNotifications
Tabelleannouncementsnotifications
Modell1 Zeile pro Event1 Zeile pro User pro Event
Read-Trackingannouncement_reads Join-Tabelleread_at Spalte
Dismissaldismissed_at in announcement_readsPhysisches Löschen
Audienceuser, workspace, all, adminImmer per-User

Standard-Ablaufzeiten

private const DEFAULT_EXPIRY_DAYS = [
'import_completed' => 7,
'daily_scrape' => 3,
'sync_completed' => 7,
'system_alert' => 30,
'account_warning' => 90,
'default' => 14,
];

E-Mail-Integration

announceToUser() prüft automatisch via NotificationPreferenceService:

  1. Ob der In-App-Kanal aktiv ist (shouldSendApp())
  2. Ob der E-Mail-Kanal aktiv ist (shouldSendEmail())
  3. Ob Quiet Hours aktiv sind (E-Mail wird dann verzögert dispatched)

Performance: SQL-Level Preference-Filterung

calculateUnreadCount() und getForUser() filtern Announcements nach User-Preferences auf SQL-Level statt im Speicher. Die private Methode getDisabledAppTypes(User $user) ermittelt deaktivierte Notification-Types und schliesst sie via ->whereNotIn('type', $disabledTypes) direkt in der Query aus. Dies verhindert OOM bei grossen Announcement-Mengen (tausende Cron-Alerts, Batch-Results etc. bei 128 MB Memory-Limit).

WICHTIG: Niemals ->get()->filter()->count() auf Announcements verwenden — immer SQL-Level-Filterung.

Abhängigkeiten

  • Announcement Model, NotificationPreferenceService, SendNotificationEmail Job, Cache

Verwendet von

  • WebSocket Event-Handler, ProfileSanitized/ProfileUnsanitized Events, Import-Completion, ServerAlertService, Admin-Aktionen

NotificationPreferenceService

Location: app/Services/Notifications/NotificationPreferenceService.php

Löst Benachrichtigungs-Präferenzen nach einer dreistufigen Hierarchie auf: Workspace-spezifisch > Global > Config-Defaults. Verwaltet Quiet Hours und bestimmt, über welche Kanäle (App/E-Mail) benachrichtigt wird.

Public Methods

MethodParameterReturnBeschreibung
resolve()User $user, string $type, ?int $workspaceIdarrayEffektive Präferenz auflösen
shouldSendApp()User $user, string $type, ?int $workspaceIdboolIn-App-Kanal aktiv?
shouldSendEmail()User $user, string $type, ?int $workspaceIdboolE-Mail-Kanal aktiv?
isInQuietHours()User $userboolIst User in Quiet Hours?
nextActiveTime()User $userCarbonWann enden die Quiet Hours?
getEmailRecipients()string $type, Collection $users, ?int $workspaceIdCollectionUser filtern die E-Mail erhalten sollen
savePreference()User $user, string $type, string $channelApp, string $channelEmail, ?int $workspaceIdNotificationPreferencePräferenz speichern
deleteWorkspaceOverride()User $user, string $type, int $workspaceIdvoidWorkspace-Override löschen
getAllForUser()User $userarrayAlle Präferenzen gruppiert nach Typ und Workspace
saveQuietHours()User $user, array $dataUserQuietHoursQuiet Hours speichern

Auflösungs-Hierarchie

1. Workspace-spezifische Präferenz (NotificationPreference mit workspace_id)
2. Globale Präferenz (NotificationPreference ohne workspace_id)
3. Config-Defaults (config('postbox.notification_types.{type}'))

Force-App-Types

Bestimmte Notification-Typen können in der Config als force_app: true markiert werden. Diese können vom User nicht deaktiviert werden und werden immer in-app angezeigt.

Quiet Hours

  • User-spezifische Zeitfenster (z.B. 22:00-07:00)
  • Timezone-aware (User-Timezone wird berücksichtigt)
  • E-Mails werden nicht verworfen, sondern auf nextActiveTime() verzögert

Abhängigkeiten

  • NotificationPreference Model, UserQuietHours Model, Config (postbox.notification_types)

Verwendet von

  • NotificationService, Settings-Seite (Notification Preferences), E-Mail-Dispatch

MailFloodGuard

Location: app/Services/Mail/MailFloodGuard.php

Circuit Breaker für ausgehende E-Mails. Trackt die Senderate via Redis-Counter und pausiert automatisch bei Überschreitung konfigurierbarer Schwellwerte. Verhindert, dass Queue-Bugs tausende E-Mails verschicken.

Public Methods

MethodParameterReturnBeschreibung
isPaused()--boolIst Mailversand aktuell pausiert?
recordAndCheck()--boolSenden zählen und Schwellwerte prüfen (true = pausiert)
pause()string $reasonboolMailversand manuell pausieren
resume()--voidMailversand fortsetzen (Counter zurücksetzen)
getStatus()--arrayAktuellen Status (Counter, Schwellwerte, Pause)
log()string $mailType, string $recipient, ...voidE-Mail im mail_log protokollieren

Schwellwerte

Config-KeyDefaultBeschreibung
postbox.mail_flood.threshold_per_minute50Max. E-Mails pro Minute
postbox.mail_flood.threshold_per_hour200Max. E-Mails pro Stunde
postbox.mail_flood.auto_pausetrueAuto-Pause bei Überschreitung

Pause-Verhalten

  • Pausierte E-Mails bleiben in der Queue und werden bei resume() nachgeliefert
  • Admin-Benachrichtigung per synchroner E-Mail (umgeht die Pause)
  • Maximale Pause-Dauer: 24 Stunden (danach automatischer Reset)
  • Admin-Notification nur 1x pro Pause-Zyklus

Fortsetzen

php artisan mail:resume
# oder über Admin-UI: /admin/log-queue

Abhängigkeiten

  • Cache (Redis), MailLog Model, Config (postbox.mail_flood.*)

Verwendet von

  • SendNotificationEmail Job (prüft isPaused() vor Versand), Admin Log & Queue Seite

MatomoTrackingService

Location: app/Services/MatomoTrackingService.php

Server-seitiges Matomo-Tracking via Queue. Alle Tracking-Calls werden als Queue-Jobs dispatched, sodass sie den HTTP-Response nicht blockieren und Matomo-Downtime via Job-Retries überbrückt wird.

Public Methods

MethodParameterReturnBeschreibung
trackPageView()string $url, string $title, ?int $userId, ...voidPage View tracken
trackEvent()string $category, string $action, string $name, ...voidCustom Event tracken
isEnabled()--boolPrüfen ob Matomo konfiguriert ist

Konfiguration

Matomo ist nur aktiv wenn alle drei Werte gesetzt sind:

  • services.matomo.enabled = true
  • services.matomo.token (Auth-Token)
  • services.matomo.url (Matomo-Server-URL)

Abhängigkeiten

  • TrackMatomoPageView Job, TrackMatomoEvent Job, Config (services.matomo.*)

Verwendet von

  • Middleware (Page View Tracking), Auth-Events, Watcher-Import-Events