A poison message needs a delivery count, not a retry limit

A retry limit lives in the consumer and resets when the consumer restarts, so a message that crashes the process is retried forever.

// the count belongs on the MESSAGE, not in the worker
if ($message->deliveryCount() >= 5) {
    $this->deadLetter($message, 'delivery count exceeded');

    return;
}

// Redis streams: XPENDING reports the delivery count
// SQS: ApproximateReceiveCount
// RabbitMQ: an x-death header, after a dead-letter round trip

A message that kills the worker never reaches the handler’s retry logic, which is exactly the message that most needs limiting. Every broker exposes a delivery count in a slightly different way and none of them make it prominent, which is why this is usually discovered by finding a queue that has been retrying the same message for a week.