affectedCustomers() as $customer) { $this->deliver($window, $customer, 'announcement', new MaintenanceAnnouncementMail($window, $customer)); } } /** Notify customers who actually received the announcement that it's cancelled. */ public function notifyCancellation(MaintenanceWindow $window): void { $ids = MaintenanceNotification::query() ->where('maintenance_window_id', $window->id) ->where('event', 'announcement') ->whereNotNull('sent_at') ->pluck('customer_id'); foreach (Customer::query()->whereIn('id', $ids)->get() as $customer) { $this->deliver($window, $customer, 'cancelled', new MaintenanceCancelledMail($window, $customer)); } } /** Queue one mail per (window, customer, event), idempotently and retryably. */ public function deliver(MaintenanceWindow $window, Customer $customer, string $event, Mailable $mail): void { $delivery = MaintenanceNotification::query()->firstOrCreate( ['maintenance_window_id' => $window->id, 'customer_id' => $customer->id, 'event' => $event], ['email' => $customer->email], ); if ($delivery->sent_at !== null) { return; // already delivered — final } if (! $delivery->wasRecentlyCreated && $delivery->updated_at->gt(now()->subMinutes(self::RETRY_AFTER_MIN))) { return; // still pending/in-flight — don't duplicate } $wasNew = $delivery->wasRecentlyCreated; $priorUpdatedAt = $delivery->updated_at; $delivery->forceFill(['updated_at' => now()])->save(); $mail->notificationId = $delivery->id; try { Mail::to($customer->email) ->locale($customer->locale ?: config('app.locale')) ->queue($mail); } catch (\Throwable $e) { $wasNew ? $delivery->delete() : $delivery->forceFill(['updated_at' => $priorUpdatedAt])->save(); throw $e; } } }