← Files Upstash RedisARCHIVED FILE
skills/upstash-qstash-js/advanced/dlq.md
4.14 KB · Oct 2, 2026 · 00:21 UTC
# Dead Letter Queue (DLQ)
Messages that fail after all retries are moved to the Dead Letter Queue for manual inspection and recovery.
## What is the DLQ?
When a message fails delivery after exhausting all retries, QStash moves it to the DLQ instead of discarding it. This lets you:
- Investigate failure reasons
- Manually retry after fixing issues
- Delete permanently failed messages
- Track patterns in failures
Common failure reasons:
- Destination endpoint errors (5XX responses)
- Timeouts
- Network issues
- Invalid responses from destination
## Listing DLQ Messages
```typescript
import { Client } from "@upstash/qstash";
const client = new Client({ token: process.env.QSTASH_TOKEN! });
const result = await client.dlq.listMessages();
console.log(`Found ${result.messages.length} failed messages`);
result.messages.forEach((msg) => {
console.log(`Message ${msg.messageId} to ${msg.url}`);
console.log(`Status: ${msg.responseStatus}`);
console.log(`DLQ ID: ${msg.dlqId}`);
});
```
## Pagination
Use cursor-based pagination for large DLQ:
```typescript
let cursor: string | undefined;
const allMessages = [];
do {
const result = await client.dlq.listMessages({
cursor,
count: 50, // Return up to 50 messages
});
allMessages.push(...result.messages);
cursor = result.cursor;
} while (cursor);
console.log(`Total failed messages: ${allMessages.length}`);
```
## Filtering DLQ Messages
Filter by various criteria:
```typescript
const result = await client.dlq.listMessages({
filter: {
messageId: "msg_123...",
url: "https://api.example.com/webhook",
urlGroup: "payment-webhooks",
queueName: "order-processing",
scheduleId: "scd_123...",
label: "payment-processing",
responseStatus: 500,
fromDate: oneDayAgo,
toDate: Date.now(),
callerIp: "192.168.1.1",
},
});
```
## Message Details
Each DLQ message includes:
```typescript
type DlqMessage = {
dlqId: string; // Unique DLQ identifier
messageId: string; // Original message ID
url: string; // Destination URL
method?: string; // HTTP method
header?: Record<string, string[]>; // Request headers
body?: string; // Request body
urlGroup?: string; // URL group name
queueName?: string; // Queue name
scheduleId?: string; // Schedule ID
createdAt: number; // Creation timestamp (ms)
notBefore?: number; // Scheduled delivery time (ms)
label?: string; // Message label
// Failure details
responseStatus?: number; // HTTP status from destination
responseHeader?: Record<string, string[]>; // Response headers
responseBody?: string; // Response body (UTF-8)
responseBodyBase64?: string; // Response body (base64 if non-UTF-8)
};
```
## Deleting Messages
```typescript
await client.dlq.delete("1725323658779-0");
await client.dlq.deleteMany({
dlqIds: ["1725323658779-0", "1725323658780-1", "1725323658781-2"],
});
```
## Understanding Failures
Inspect failure details:
```typescript
const result = await client.dlq.listMessages();
for (const msg of result.messages) {
console.log(`\nMessage ${msg.messageId}:`);
console.log(`URL: ${msg.url}`);
console.log(`Status: ${msg.responseStatus}`);
if (msg.responseBody) {
console.log(`Response: ${msg.responseBody}`);
} else if (msg.responseBodyBase64) {
const decoded = Buffer.from(msg.responseBodyBase64, "base64").toString();
console.log(`Response: ${decoded}`);
}
if (msg.responseHeader) {
console.log(`Headers:`, msg.responseHeader);
}
}
```
## Using Failure Callbacks
Instead of polling the DLQ, use failure callbacks for real-time notifications:
See [Callbacks](callbacks.md) for more details.
## DLQ Retention
Messages remain in the DLQ based on your plan:
- **Free**: 7 days
- **Paid**: Check your plan on [QStash Pricing](https://upstash.com/pricing/qstash)
Messages are automatically deleted when retention expires.
## Best Practices
- Set up failure callbacks for critical messages
- Regularly monitor DLQ for patterns
- Delete non-retriable messages to keep DLQ clean
- Use labels to categorize and filter failures
- Alert on DLQ message count thresholds
- Document common failure scenarios and resolutions
- Consider automated retries for known transient issues
SHA-256: 94d576cce41239ba15bb14fb375b8eb0d886fa6c4ea38360e94885b9a47ee082