Note: This document captures user requirements and needs. Technical design decisions and implementation details should be documented in an Architecture Decision Record (ADR) in
docs/adr/.
Linked ADR: ADR 0034: Provide DLQ Where Missing Reference Implementation: Spec 0001: Kafka Dead Letter Queue
The PostgreSQL message consumer (PostgresMessageConsumer) currently handles message rejection by deleting the message from the queue table. There is no dead letter queue support - rejected messages are permanently lost.
When a handler throws RejectMessageAction, the message is deleted with no way to inspect or replay it. There is no support for routing rejected messages to a dead letter channel or invalid message channel.
Add Brighter-managed DLQ support following the Kafka pattern:
PostgresSubscriptionimplementsIUseBrighterDeadLetterSupportandIUseBrighterInvalidMessageSupportPostgresConsumerFactoryextracts routing keys from the subscription and passes them to the consumerPostgresMessageConsumer.Reject()andRejectAsync()send messages to DLQ/Invalid Message channels using a lazyIAmAMessageProducer, then delete the original- Messages are enriched with metadata before being sent to DLQ
- When
Reject()is called with aDeliveryErrorreason, the message should be sent to the configured dead letter channel (a separate PostgreSQL queue topic) before deleting the original - When
Reject()is called with anUnacceptablereason, the message should be sent to the configured invalid message channel, falling back to the DLQ if no invalid message channel is configured - After sending to DLQ, the original message must be deleted from the source queue (current behaviour preserved)
- The dead letter message should include metadata: original topic, rejection reason, rejection timestamp, original message type
- DLQ routing key should be configurable per subscription via
IUseBrighterDeadLetterSupport - Invalid Message routing key should be configurable per subscription via
IUseBrighterInvalidMessageSupport - If no DLQ or Invalid Message channel is configured, rejection should delete the message (current behaviour) and log a warning
- All new constructor parameters must be optional to maintain backward compatibility
- Performance: Lazy producer initialization to avoid overhead when DLQ is not used
- Reliability: If DLQ production fails, log the error and delete the original message to prevent blocking the consumer
- Observability: DLQ operations should be logged with structured logging
- Consistency: Follow the same pattern as Kafka DLQ implementation
- Automatic replay/retry from DLQ
- DLQ message format transformation
- When a handler throws
RejectMessageAction, the message is sent to the configured DLQ channel before deletion - The DLQ message contains the original message payload and rejection metadata
- Users can configure DLQ and Invalid Message channel names per subscription
- The original message is still deleted from the source queue after DLQ send
- Existing tests pass and new tests cover DLQ scenarios
- All new parameters are optional (backward compatible)
- Integration tests with PostgreSQL for end-to-end DLQ flow
- Tests for rejection routing (DeliveryError → DLQ, Unacceptable → Invalid Message)
- Tests for fallback behaviour (Unacceptable with no Invalid Message channel → DLQ)
- Tests for no-channel-configured behaviour (delete and log)