githubEdit

Error Handling Options

Brighter's message pump uses Subscription properties to control how it handles errors. These properties configure requeue behavior, Dead Letter Queue (DLQ) routing, and pump termination thresholds.

This document covers the configuration side of error handling. For an overview of strategies and when to use each one, see Error Handling.

Subscription Properties

RequeueCount

Type

int

Default

-1 (unlimited)

The maximum number of times a message can be requeued (via DeferMessageAction) before the pump treats it as a poison message. When the count is exceeded, the message is rejected — routed to the DLQ if one is configured, or acknowledged and discarded otherwise.

Set this to a positive integer to prevent infinite retry loops. A value of -1 disables the limit, allowing unlimited requeues. A value of 0 means the message is rejected on the first DeferMessageAction without any requeue.

RequeueDelay

Type

TimeSpan

Default

TimeSpan.Zero

How long a requeued message waits before becoming visible on the channel again. How the delay is implemented depends on the transport — some support native delay, others rely on a configured scheduler (IAmARequestScheduler).

By default, Brighter provides an InMemoryScheduler that holds deferred messages in memory. If the host shuts down before a deferred message fires, it is lost. For durable scheduling in production, configure an external scheduler such as Quartz, Hangfire, TickerQ, or AWS Scheduler.

UnacceptableMessageLimit

Type

int

Default

0 (disabled)

The number of unacceptable messages the pump tolerates before shutting down. An unacceptable message is one that triggers a RejectMessageAction, DontAckAction, InvalidMessageAction, or any unhandled exception. DeferMessageAction does not count.

Set this to a positive integer to protect against mass message loss during systemic failures — for example, a database outage that causes every message to fail and be rejected to the DLQ. A value of 0 or less disables the limit.

UnacceptableMessageLimitWindow

Type

TimeSpan?

Default

null (count never resets)

The time window over which unacceptable messages are counted. The count resets at the end of each window.

Without a window, the count accumulates over the pump's entire lifetime. Even a low rate of errors will eventually reach the limit and shut down the pump. Setting a window means only bursts of errors during a single window trigger a shutdown.

DontAckDelay

Type

TimeSpan

Default

TimeSpan.FromSeconds(1)

The delay the pump waits after a DontAckAction before processing the next message. This prevents tight-loop CPU burn when a message is repeatedly not acknowledged.

This property is set on the MessagePump directly, not on the Subscription.

Configuration Example

Dead Letter Queue Configuration

How DLQ Routing Works

When a message is rejected (via RejectMessageAction, or when RequeueCount is exceeded), the message pump decides where to send it:

  1. Delivery errors (RejectMessageAction, requeue count exceeded): routed to the DeadLetterRoutingKey channel if configured, or acknowledged and discarded otherwise.

  2. Invalid messages (InvalidMessageAction): routed to the InvalidMessageRoutingKey channel if configured, falling back to DeadLetterRoutingKey, or acknowledged and discarded if neither is set.

Subscriptions that support Brighter-managed DLQ implement IUseBrighterDeadLetterSupport. Those that also support separate invalid message routing implement IUseBrighterInvalidMessageSupport.

Native vs Brighter-Managed DLQ

Some transports provide native DLQ support. For transports without native DLQ, Brighter manages the routing by producing rejected messages to a separate channel.

Transport
DLQ Type
Invalid Message
Notes

RabbitMQ

Native (Dead Letter Exchange)

Brighter-managed

Uses DLX with routing key

AWS SQS

Brighter-managed

Brighter-managed

Direct send to DLQ queue

Azure Service Bus

Native

Brighter-managed

Built-in DLQ per subscription

Kafka

Brighter-managed

Brighter-managed

Lazy producer to DLQ topic

Redis

Brighter-managed

Brighter-managed

No native DLQ (BLPOP is destructive)

MsSql

Brighter-managed

Brighter-managed

Same table, different topic value

PostgreSQL

Brighter-managed

Brighter-managed

Visibility timeout model

MQTT

Brighter-managed

Brighter-managed

Fire-and-forget, no ack concept

Configuring DLQ on a Subscription

Set deadLetterRoutingKey and optionally invalidMessageRoutingKey on your subscription. Both are constructor parameters available on all transport-specific subscription types.

Kafka subscription with DLQ and invalid message routing:

SQS subscription with DLQ:

DLQ Naming Conventions

Brighter provides DeadLetterNamingConvention and InvalidMessageNamingConvention helper classes to generate consistent channel names from a source topic. The default templates are {0}.dlq and {0}.invalid respectively.

Message Enrichment

When a message is routed to a DLQ or invalid message channel, Brighter adds metadata to the message header bag:

Header
Description

OriginalTopic

The topic the message was originally consumed from

RejectionReason

DeliveryError (handler failure) or Unacceptable (deserialization failure)

RejectionTimestamp

UTC ISO-8601 timestamp of when the message was rejected

OriginalMessageType

The original message type before rejection

RejectionMessage

Description of the rejection reason (if provided)

This metadata helps operators investigate why a message was rejected and trace it back to its source.

Common Configurations

Retry 3 Times Then DLQ

A common pattern: requeue the message up to 3 times with a delay, then route to the DLQ. Combine with RejectMessageOnErrorAttribute on the handler to ensure unhandled exceptions also go to the DLQ.

With this configuration, a handler that throws DeferMessageAction will requeue the message up to 3 times. On the 4th failure, the message is rejected and routed to orders.dlq.

Stop Pump After 10 Errors in 5 Minutes

Protect against mass message loss during systemic failures by limiting the number of unacceptable messages within a time window.

With this configuration, if 10 or more messages are rejected, nacked, or fail with unhandled exceptions within a 5-minute window, the pump shuts down. This gives operators time to investigate before more messages are lost. If errors are spread out — fewer than 10 per window — the pump continues running.

Further Reading

Last updated

Was this helpful?