# DLQ Reference: Reasons, Entry Shape, Methods

Reference for bunqueue Dead Letter Queue reasons, reserved categories, the full entry structure with attempt history, and the complete method surface.

Canonical: https://bunqueue.dev/guide/dlq/reference/

---

<div class="bq-wrap bq-hero">
  <span class="bq-eyebrow">guide · dead letter queue</span>
  <h1 class="bq-hero-h1 bq-bench-h1">Every reason, every <em>field.</em></h1>
  <p class="bq-hero-sub">What each failure reason means, what an entry actually contains once it is written, and the full list of DLQ methods with their embedded and TCP behaviour.</p>
</div>

## Reference

### Why jobs end up in the DLQ

| Reason | Description |
|--------|-------------|
| `explicit_fail` | A retryable processor failure in the attempt history |
| `max_attempts_exceeded` | A normal processor failure exhausted the job's attempts |
| `timeout` | A processing timeout; retained on retry attempts and terminal timeout entries |
| `stalled` | Job stopped sending heartbeats (worker likely crashed) |
| `ttl_expired` | Reserved category; current waiting-job TTL expiry removes the job instead of creating a DLQ entry |
| `worker_lost` | Reserved category; current disconnect/lock recovery is classified as `stalled` |
| `unknown` | Fallback for unclassified failures |

The entry's `reason` is the terminal cause. Its `attempts` array preserves the
cause of every failed attempt, so a timeout followed by a normal processor
failure is represented as `[timeout, max_attempts_exceeded]`, while two
consecutive timeouts remain `[timeout, timeout]`.

### Entry structure

```typescript
interface DlqEntry<T> {
  job: Job<T>;                    // The failed job
  enteredAt: number;              // When first moved to DLQ
  reason: FailureReason;          // Why it failed
  error: string | null;           // Error message
  attempts: AttemptRecord[];      // Full attempt history
  retryCount: number;             // Times retried from DLQ
  lastRetryAt: number | null;     // Last DLQ retry time
  nextRetryAt: number | null;     // Next scheduled auto-retry
  expiresAt: number | null;       // When entry expires
}
```

Each `AttemptRecord` carries the attempt number, start and failure timestamps, failure reason, error message, and duration in ms.

### Bun Queue methods

| Method | Result | Runtime behavior |
|--------|--------|------------------|
| `getDlq(filter?)` | `DlqEntry[]` | Synchronous embedded snapshot |
| `getDlqAsync(filter?)` | `Promise<DlqEntry[]>` | Authoritative embedded or TCP read |
| `getDlqStatsAsync()` | `Promise<DlqStats>` | Authoritative embedded or TCP statistics |
| `retryDlqAsync(id?)` | `Promise<number>` | Re-queues selected entries and returns the count |
| `retryDlqByFilterAsync(filter)` | `Promise<number>` | Re-queues matching entries |
| `removeDlqJob(id)` | `Promise<boolean>` | Permanently removes one job; rejects broker errors |
| `removeDlqJobAsync(id)` | `Promise<boolean>` | Explicit alias of `removeDlqJob` |
| `purgeDlqAsync()` | `Promise<number>` | Permanently removes every entry and returns the count |

Selective removal is idempotent: `false` means the entry did not exist. It is
not an error fallback. A failed durable delete rejects and leaves the DLQ entry
authoritative.

:::tip[Related Guides]
- [Stall Detection & Recovery](/guide/stall-detection/) - Stalled jobs are sent to the DLQ
- [Worker API](/guide/worker/) - Configure retry behavior
- [Monitoring & Prometheus Metrics](/guide/monitoring/) - Alert on DLQ size
:::

## Where to go next

| | |
|---|---|
| [DLQ Operations from the Queue Object](/guide/queue/dlq/) | The same operations from an existing Queue instance |
| [Dead Letter Queue](/guide/dlq/) | What the DLQ is, and a first look at what failed |
| [DLQ Operations](/guide/dlq/operations/) | Filter, retry selectively, check health, purge |
| [Automatic DLQ Retry with Backoff](/guide/dlq/auto-retry/) | Let bunqueue re-queue dead entries on a backoff |
| [DLQ Configuration](/guide/dlq/configuration/) | autoRetry, maxAge, maxEntries and the defaults |