Dead-Letter Queue (DLQ)
Open Source and EnterpriseWhen a pipeline component fails to process a message, that message lands in the pipeline’s dead-letter queue (DLQ) instead of being dropped. Each entry records the component that failed, the error, and the original payload. The Enterprise list endpoint also assigns each message a stable message_id.
Open Source GlassFlow lets you inspect the DLQ and purge it. The Enterprise Edition adds a non-destructive paginated read, reprocessing (replaying failed messages through the pipeline), and selective discard.
Editions. Inspecting and purging the DLQ are available in Open Source. Listing with stable message IDs, reprocessing, and selective discard are part of the Enterprise Edition and are marked with an Enterprise badge below. To get access, reach out to our team .
Examples are shown for the Python SDK and the REST API. A Web UI for DLQ management is on the way; once it ships, the examples below will gain a Web UI tab with step-by-step screenshots.
All endpoints live under /api/v1/pipeline/{pipeline_id}/dlq. The examples use http://localhost:30180 as the host, the default for a local CLI install, and the Python SDK client:
# Open Source client
from glassflow.etl import Client
# Enterprise client (adds list, reprocess, discard)
from glassflow.ee import Client
client = Client(host="http://localhost:30180")
pipeline = client.get_pipeline(pipeline_id="my-pipeline")DLQ state
Get a summary of the queue: totals and the last received and consumed timestamps.
Python SDK
print(pipeline.dlq.state())Response:
{
"last_consumed_at": "2026-05-20T10:32:38.113427621Z",
"last_received_at": "2026-05-20T10:23:26.530466989Z",
"total_messages": 2,
"unconsumed_messages": 1
}Reading messages
GlassFlow provides two endpoints for reading the DLQ: consume in Open Source, and list in the Enterprise Edition, which is non-destructive and returns stable message IDs.
Consume
consume reads a batch of messages from the DLQ. It is available in Open Source.
Python SDK
messages = pipeline.dlq.consume(batch_size=50)Response:
[
{
"component": "ingestor",
"error": "failed to validate data",
"original_message": "<original message>"
}
]consume remains a supported endpoint in Open Source. In the Enterprise Edition it is deprecated in favour of list (below), which is non-destructive and returns stable message IDs. The Python SDK’s consume() is likewise deprecated on the Enterprise client and forwards to list().
List
Enterpriselist is a non-destructive, paginated read. It leaves messages in place and assigns each a stable message_id (the value you pass to reprocess and discard), alongside component, error, original_message, and received_at.
The response is a page object: messages plus has_more, and a next_cursor when more pages remain. Pass next_cursor back as the cursor parameter to fetch the next page. Filter to a single component with the component parameter, one of ingestor, join, sink, dedup, otlp-receiver.
Python SDK
# One page, optionally filtered by component
page = pipeline.dlq.list(batch_size=50, component="sink")
for message in page["messages"]:
print(message["message_id"], message["component"], message["error"])
# Or iterate every message, paging through the cursor for you
for message in pipeline.dlq.list_iter(component="sink"):
print(message["message_id"], message["error"])Response:
{
"messages": [
{
"message_id": "101",
"component": "sink",
"error": "connection refused",
"original_message": "{...}",
"received_at": "2026-05-29T14:00:00Z"
}
],
"next_cursor": "101",
"has_more": true
}Reprocessing
EnterpriseReprocess moves messages from the DLQ back to the start of the pipeline (the source input), so they flow through the whole pipeline again. Use it after fixing a transient failure, for example a ClickHouse outage, or after fixing a transform bug. It returns 202 Accepted; the replay happens asynchronously.
Reprocessing requires a running pipeline. Reprocess replays messages through the running pipeline, so the pipeline must be in the Running state. Discard, by contrast, works in any state because it acts on the queue directly.
Python SDK
# Reprocess everything currently in the queue
pipeline.dlq.reprocess_all()
# Reprocess specific messages by message_id
pipeline.dlq.reprocess(["101", "102"])Response:
{
"request_id": "dlq-reprocess-a1b2c3d4-5e6f-7890-abcd-ef0123456789",
"status": "accepted"
}status is accepted when every source component acknowledges the request, or partial when only some do.
Discarding
EnterpriseDiscard permanently removes messages without reprocessing them. Use it for genuinely malformed events you do not want to retry.
Python SDK
# Discard specific messages
pipeline.dlq.discard(["200"])
# Discard everything currently in the queue
pipeline.dlq.discard_all()Response:
{
"request_id": "dlq-discard-b2c3d4e5-6f70-8901-bcde-f01234567890",
"discarded_count": 1
}Purging
purge removes every message from the DLQ. It is available in Open Source and returns no body. In the Enterprise Edition it is superseded by discard_all (discard with mode: all), which reports how many messages were removed.
Python SDK
pipeline.dlq.purge()Modes, limits, and behavior
EnterpriseReprocess and discard both support two modes:
-
allacts on every message currently in the DLQ, up to the queue head at the time of the request. Messages that arrive after the request are left for the next call. -
selectedacts only on themessage_idvalues you pass. -
A
selectedrequest accepts at most 1000message_idvalues. -
mode: allprocesses up to 10,000 messages per request with a 60 second timeout. For larger queues, page through the messages and useselectedrequests. -
Reprocessing a
message_idthat no longer exists is not an error. The missing ID is reported back and the remaining IDs are still processed. -
mode: allon an empty DLQ returns a count of zero rather than an error.