get($this->typeKey); if ($type?->digestMail === null) { return; } // Locked and deleted inside a transaction, before anything is // sent — sending is itself queued, so it must happen after this // commits rather than from inside it. $items = DB::transaction(function (): ?Collection { $pending = PendingNotification::query() ->where('user_id', $this->userId) ->where('type', $this->typeKey) ->lockForUpdate() ->orderBy('id') ->get(); if ($pending->isEmpty()) { return null; } PendingNotification::query()->whereIn('id', $pending->pluck('id'))->delete(); return $pending; }); if ($items === null || $items->isEmpty()) { return; } $user = User::query()->find($this->userId); if ($user === null) { return; } if ($items->count() === 1 || $type->digestMailMany === null) { /** @var PendingNotification $item */ $item = $items->first(); // Each single-item mail class exposes from(), because the row // carries its payload in a generic `context` and only that // class knows how to read its own. $user->notify($type->digestMail::from($item)); return; } $user->notify(new $type->digestMailMany(array_values($items->all()))); } }