- Docs
- Dead Letter Queue
- Operations
Read it, retry it, clear it.
The four things you actually do with a Dead Letter Queue: narrow it down to the entries you care about, put a subset back, check whether the pile is growing, and empty it when the cause is fixed.
Common Tasks
Section titled “Common Tasks”Filter entries
Section titled “Filter entries”await queue.getDlqAsync({ reason: 'max_attempts_exceeded' }); // by emitted reasonawait queue.getDlqAsync({ olderThan: Date.now() - 86400000 }); // older than 24hawait queue.getDlqAsync({ newerThan: Date.now() - 3600000 }); // last hourawait queue.getDlqAsync({ retriable: true }); // auto-retry is due nowawait queue.getDlqAsync({ limit: 10, offset: 20 }); // paginationThe SDK’s getDlq() returns jobs only. Use the HTTP endpoint for full entry
metadata, then filter the returned page in the client:
const response = await fetch('http://localhost:6790/queues/emails/dlq?limit=100&offset=0');const { entries, total } = await response.json();const stalled = entries.filter((entry) => entry.reason === 'stalled');The SDK’s get_dlq() returns jobs only. The standard library can read full
entries from HTTP:
import jsonfrom urllib.request import urlopen
with urlopen("http://localhost:6790/queues/emails/dlq?limit=100&offset=0") as response: payload = json.load(response)stalled = [entry for entry in payload["entries"] if entry["reason"] == "stalled"]The SDK’s getDlq() returns jobs only. Read full entries through HTTP:
$json = file_get_contents('http://localhost:6790/queues/emails/dlq?limit=100&offset=0');$payload = json_decode($json, true, flags: JSON_THROW_ON_ERROR);$stalled = array_filter($payload['entries'], fn ($entry) => $entry['reason'] === 'stalled');The SDK’s GetDlq() returns jobs only. Read full entries through HTTP with the
standard library:
response, err := http.Get("http://localhost:6790/queues/emails/dlq?limit=100&offset=0")if err != nil { return err }defer response.Body.Close()
var payload struct { Entries []map[string]any `json:"entries"` }err = json.NewDecoder(response.Body).Decode(&payload)The Rust SDK’s get_dlq() returns jobs only. Use the broker HTTP endpoint (and
your application’s HTTP/JSON crate) for full entries:
curl 'http://localhost:6790/queues/emails/dlq?limit=100&offset=0'The Elixir SDK’s dlq/2 returns jobs only. Use the broker HTTP endpoint for
full entries:
curl 'http://localhost:6790/queues/emails/dlq?limit=100&offset=0'The Bun async API applies reason, time, due-retry, expiry, limit, and offset
filters server-side in both embedded and TCP modes. The HTTP endpoint currently
supports only limit and offset; external clients must filter that page
locally. retriable: true means nextRetryAt is already due, not merely that
the entry has retry budget remaining.
Retry selectively
Section titled “Retry selectively”queue.retryDlq(); // retry everythingqueue.retryDlq('job-123'); // retry one jobqueue.retryDlqByFilter({ reason: 'stalled' }); // TCP is fire-and-forgetconst filtered = await queue.retryDlqByFilterAsync({ reason: 'stalled' }); // authoritative countconst n = await queue.retryDlqAsync(); // retry and get the count (TCP too)await queue.retryDlq(); // retry everythingawait queue.retryDlq('job-123'); // retry one jobawait queue.retryJobs({ count: 100 }); // retry only the first 100 entriesqueue.retry_dlq() # retry everythingqueue.retry_dlq("job-123") # retry one jobqueue.retry_dlq(count=100) # retry only the first 100 entries$queue->retryDlq(); // retry everything$queue->retryDlq('job-123'); // retry one job$queue->retryDlq(null, 100); // retry only the first 100 entriesn, err := queue.RetryDlq("", 0) // retry everythingn, err = queue.RetryDlq("job-123", 0) // retry one jobn, err = queue.RetryDlq("", 100) // retry only the first 100 entriesqueue.retry_dlq(None, None)?; // retry everythingqueue.retry_dlq(Some("job-123"), None)?; // retry one jobqueue.retry_dlq(None, Some(100))?; // retry only the first 100 entries{:ok, n} = Bunqueue.Queue.retry_dlq(queue) # retry everything{:ok, _} = Bunqueue.Queue.retry_dlq(queue, "job-123") # retry one job{:ok, _} = Bunqueue.Queue.retry_dlq(queue, nil, 100) # retry only the first 100Check DLQ health
Section titled “Check DLQ health”const stats = await queue.getDlqStatsAsync();console.log(stats.total); // total entriesconsole.log(stats.byReason); // { max_attempts_exceeded: 5, stalled: 2, ... }console.log(stats.pendingRetry); // entries whose auto-retry time is dueA simple alert loop:
setInterval(async () => { const stats = await queue.getDlqStatsAsync(); if (stats.total > 100) alertOps('High DLQ count', stats);}, 30000);const { stats } = await (await fetch( 'http://localhost:6790/queues/emails/dlq/stats')).json();with urlopen("http://localhost:6790/queues/emails/dlq/stats") as response: stats = json.load(response)["stats"]$payload = json_decode( file_get_contents('http://localhost:6790/queues/emails/dlq/stats'), true, flags: JSON_THROW_ON_ERROR,);$stats = $payload['stats'];response, err := http.Get("http://localhost:6790/queues/emails/dlq/stats")// Decode response.Body and read the top-level "stats" object.curl 'http://localhost:6790/queues/emails/dlq/stats'curl 'http://localhost:6790/queues/emails/dlq/stats'Use synchronous getDlqStats() for a Bun embedded snapshot and
getDlqStatsAsync() for an authoritative Bun result in either runtime. The
other SDKs do not expose a stats helper yet; the HTTP result is authoritative.
Permanently remove one failed job
Section titled “Permanently remove one failed job”const removed = await queue.removeDlqJob('job-123');// true: the selected DLQ entry was deleted// false: it was already absentremoveDlqJobAsync(id) is an explicit alias with the same
Promise<boolean> contract. The operation does not retry the job. It removes
the durable entry and terminal auxiliary state before resolving, including any
recovered duplicate rows for the same queue and job ID. Broker and persistence
errors reject the Promise; only a successful miss resolves false.
const purged = queue.purgeDlq(); // permanently deletes all entriesconst n = await queue.purgeDlqAsync(); // same, but waits and returns the count (TCP too)const purged = await queue.purgeDlq(); // permanently deletes all entries, returns the countpurged = queue.purge_dlq() # permanently deletes all entries, returns the count$purged = $queue->purgeDlq(); // permanently deletes all entries, returns the countpurged, err := queue.PurgeDlq() // permanently deletes all entries, returns the countlet purged = queue.purge_dlq()?; // permanently deletes all entries, returns the count{:ok, purged} = Bunqueue.Queue.purge_dlq(queue) # deletes all entries, returns the countWhere to go next
Section titled “Where to go next”| DLQ Operations from the Queue Object | The same operations from an existing Queue instance |
| Dead Letter Queue | What the DLQ is, and a first look at what failed |
| Automatic DLQ Retry with Backoff | Let bunqueue re-queue dead entries on a backoff |
| DLQ Configuration | autoRetry, maxAge, maxEntries and the defaults |
| DLQ Reference | Failure reasons, entry shape, every DLQ method |