diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 43f2575..98f0e44 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -49,28 +49,27 @@ class AppServiceProvider extends ServiceProvider public function boot(): void { // Send-time guard for maintenance mail (X-CP-Notification carries the - // ledger id). Two things happen atomically here so exactly one copy ships - // even if a backlog caused a duplicate job to be queued: - // 1) suppress an announcement whose window was cancelled meanwhile; - // 2) claim delivery with a conditional update — the first send wins, - // any duplicate finds sent_at already set and is aborted. + // ledger id). Read-only — it never marks the row sent (that happens on + // real delivery in MessageSent, so a transport failure stays retryable). + // It aborts a send when the window was cancelled meanwhile, or when a + // sibling copy already delivered (sent_at set) — the common backlog dedup. Event::listen(MessageSending::class, function (MessageSending $event) { $header = $event->message->getHeaders()->get('X-CP-Notification'); if ($header === null) { return null; } - $id = (int) $header->getBodyAsString(); - $notification = MaintenanceNotification::query()->with('window')->find($id); + $notification = MaintenanceNotification::query()->with('window')->find((int) $header->getBodyAsString()); if ($notification === null) { return null; } + if ($notification->sent_at !== null) { + return false; // already delivered — suppress a duplicate copy + } if ($notification->event === 'announcement' && $notification->window?->state === 'cancelled') { return false; // window cancelled meanwhile — do not deliver } - $claimed = MaintenanceNotification::query()->whereKey($id)->whereNull('sent_at')->update(['sent_at' => now()]); - - return $claimed === 0 ? false : null; // 0 → already delivered, suppress duplicate + return null; }); // Stamp a maintenance-notification ledger row as delivered only once the