Skip to main content

Retries & dead letters

Whether a failed event gets retried or dead-lettered comes down to which exception your handler raises.

The two exceptions

from pymidil.event.exceptions import RetryableEventError, NonRetryableEventError

class OrderSubscriber(EventSubscriber):
async def handle(self, event) -> None:
if upstream_busy():
raise RetryableEventError("upstream throttled") # → redelivered with backoff
if event.body.get("amount", 0) < 0:
raise NonRetryableEventError("invalid amount") # → dead-lettered, no retry
await process(event)
  • RetryableEventError: the event is requeued and redelivered after a backoff delay.
  • NonRetryableEventError (or any other unhandled exception): the event is moved straight to the dead-letter queue. Use this deliberately once you've decided a message will never succeed no matter how many times it's retried (bad data, a permanently missing reference, and so on).

If you have multiple subscribers on the same event and any one of them raises a retryable error, the whole delivery is retried, even if another subscriber succeeded or raised a non-retryable error.

Backoff

The built-in SQSConsumer retries with exponential backoff, configurable per consumer:

SQSConsumerEventConfig(
type="sqs",
queue_url="...",
dlq_url="...",
backoff_base_delay=5, # seconds before the first retry
backoff_max_delay=300, # backoff never grows past this
)

There's no built-in maximum attempt count. A RetryableEventError keeps getting redelivered (with growing backoff) until it either succeeds or you decide it should stop and raise a non-retryable error instead. If your queue has its own redrive policy configured on your broker, that acts as an outer safety net independent of the SDK.

If you're observing an existing consumer

ConsumerObserver doesn't decide retry/dead-letter for you. That's already up to your existing broker client and loop. What it needs from you is how to classify an outcome, so the right status reaches the dashboard: report a failure as retryable or not based on whatever your own code already knows (an exception type, an error code, a delivery-count header from your broker).

Where it shows up

Every attempt is visible in Event Detail's Retry Attempts tab, and anything currently dead-lettered is grouped by failure class in the DLQ Center.