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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
getForUser() | User $user, int $limit = 20 | Collection | Alle Benachrichtigungen für User (merged + sortiert) |
getPaginatedForUser() | User $user, int $perPage = 20 | LengthAwarePaginator | Paginierte Benachrichtigungen |
getUnreadCount() | User $user | int | Ungelesene Anzahl (gecacht, 5 Min TTL) |
calculateUnreadCount() | User $user | int | Ungelesene Anzahl (direkt berechnet) |
markAsRead() | User $user, string $source, string|int $id | void | Einzelne Benachrichtigung als gelesen markieren |
markAsUnread() | User $user, string $source, string|int $id | void | Einzelne Benachrichtigung als ungelesen markieren |
markAllAsRead() | User $user | void | Alle Benachrichtigungen als gelesen markieren (nutzt chunkById(5000) für Announcements wegen PostgreSQL 65.535 Parameter-Limit) |
delete() | User $user, string $source, string|int $id | void | Notification löschen / Announcement dismissen |
announceToUser() | User $user, string $type, array $data, ?int $expiryDays, ?int $workspaceId | Announcement | Announcement für einzelnen User erstellen |
announceToWorkspace() | Workspace $workspace, string $type, array $data, ?int $expiryDays | Announcement | Announcement für Workspace erstellen |
announceToAll() | string $type, array $data, bool $adminOnly, ?int $expiryDays | Announcement | Announcement für alle User/Admins erstellen |
invalidateUnreadCountCache() | User $user | void | Unread-Count-Cache invalidieren |
Announcements vs. Notifications
| Eigenschaft | Announcements | Notifications |
|---|---|---|
| Tabelle | announcements | notifications |
| Modell | 1 Zeile pro Event | 1 Zeile pro User pro Event |
| Read-Tracking | announcement_reads Join-Tabelle | read_at Spalte |
| Dismissal | dismissed_at in announcement_reads | Physisches Löschen |
| Audience | user, workspace, all, admin | Immer 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:
- Ob der In-App-Kanal aktiv ist (
shouldSendApp()) - Ob der E-Mail-Kanal aktiv ist (
shouldSendEmail()) - 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
AnnouncementModel,NotificationPreferenceService,SendNotificationEmailJob, Cache
Verwendet von
- WebSocket Event-Handler,
ProfileSanitized/ProfileUnsanitizedEvents, 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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
resolve() | User $user, string $type, ?int $workspaceId | array | Effektive Präferenz auflösen |
shouldSendApp() | User $user, string $type, ?int $workspaceId | bool | In-App-Kanal aktiv? |
shouldSendEmail() | User $user, string $type, ?int $workspaceId | bool | E-Mail-Kanal aktiv? |
isInQuietHours() | User $user | bool | Ist User in Quiet Hours? |
nextActiveTime() | User $user | Carbon | Wann enden die Quiet Hours? |
getEmailRecipients() | string $type, Collection $users, ?int $workspaceId | Collection | User filtern die E-Mail erhalten sollen |
savePreference() | User $user, string $type, string $channelApp, string $channelEmail, ?int $workspaceId | NotificationPreference | Präferenz speichern |
deleteWorkspaceOverride() | User $user, string $type, int $workspaceId | void | Workspace-Override löschen |
getAllForUser() | User $user | array | Alle Präferenzen gruppiert nach Typ und Workspace |
saveQuietHours() | User $user, array $data | UserQuietHours | Quiet 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
NotificationPreferenceModel,UserQuietHoursModel, 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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
isPaused() | -- | bool | Ist Mailversand aktuell pausiert? |
recordAndCheck() | -- | bool | Senden zählen und Schwellwerte prüfen (true = pausiert) |
pause() | string $reason | bool | Mailversand manuell pausieren |
resume() | -- | void | Mailversand fortsetzen (Counter zurücksetzen) |
getStatus() | -- | array | Aktuellen Status (Counter, Schwellwerte, Pause) |
log() | string $mailType, string $recipient, ... | void | E-Mail im mail_log protokollieren |
Schwellwerte
| Config-Key | Default | Beschreibung |
|---|---|---|
postbox.mail_flood.threshold_per_minute | 50 | Max. E-Mails pro Minute |
postbox.mail_flood.threshold_per_hour | 200 | Max. E-Mails pro Stunde |
postbox.mail_flood.auto_pause | true | Auto-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),
MailLogModel, Config (postbox.mail_flood.*)
Verwendet von
SendNotificationEmailJob (prüftisPaused()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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
trackPageView() | string $url, string $title, ?int $userId, ... | void | Page View tracken |
trackEvent() | string $category, string $action, string $name, ... | void | Custom Event tracken |
isEnabled() | -- | bool | Prüfen ob Matomo konfiguriert ist |
Konfiguration
Matomo ist nur aktiv wenn alle drei Werte gesetzt sind:
services.matomo.enabled= trueservices.matomo.token(Auth-Token)services.matomo.url(Matomo-Server-URL)
Abhängigkeiten
TrackMatomoPageViewJob,TrackMatomoEventJob, Config (services.matomo.*)
Verwendet von
- Middleware (Page View Tracking), Auth-Events, Watcher-Import-Events