Feature: DLQ Admin Surface
Reference: the SuperAdmin Dead Letter Queue surface for outbound channel dispatch failures โ what lands there, what the UI shows, and the retry / drain / inspect operations. Last updated: 2026-06-29 Audience: SuperAdmin, Ops, Engineering, QA Surface:
/ops/dlq(Ops โ SuperAdmin only) Related: DLQ Handling runbook, BullMQ Queue Backlog runbook, Notifications, API reference โ Admin ยท DLQ
TL;DRโ
When a canonical Hermes reply or genuine Expert-authored follow-up can't be delivered to a client's channel (Slack / WhatsApp / Telegram / Email), the dispatch isn't silently lost โ it is persisted to the channel_failures Dead Letter Queue (DLQ). A background worker retries it on a schedule; anything it can't recover surfaces at /ops/dlq for a human to inspect, replay, or drop.
The DLQ is the safety net behind the ChannelDispatcherService three-layer outbound path: withCircuit โ withRetry โ channelFailures.push. The first two layers handle transient blips; the DLQ catches what survives them so a message never just disappears.
When items hit the DLQโ
A row is written to channel_failures (status queued) whenever ChannelDispatcherService exhausts its in-process protection and the .catch(channelFailures.push) fires. Typical causes:
| Cause | Example error | Notes |
|---|---|---|
| Vendor outage / 5xx | circuit breaker open, ETIMEDOUT | The circuit breaker trips after 5 consecutive failures; subsequent sends fast-fail straight to the DLQ until it resets. |
| Auth / credential failure | Slack invalid_auth, token_revoked | Slack permanent errors (channel_not_found, not_in_channel, token_revoked, โฆ) are promoted straight to dead โ they can never be redelivered (slack-errors.util.ts, #3059). |
| Rate limiting | 429, too many requests | Retry after the provider cooldown clears. |
| Email delivery failure | Resend / SMTP / delivery errors | Check Resend status + the org's sending domain. |
| Timeout | timed out reaching the channel provider | Often transient; the retry worker usually clears these. |
Not every failed send reaches the DLQ. A reply to a disconnected client Slack workspace is marked failed but deliberately not enqueued โ re-driving it would loop forever (channel-dispatcher.service.ts, #2403).
Scope / privacy. DLQ payloads contain raw outbound content โ customer phone numbers, email addresses, message bodies. The surface is SuperAdmin only (
JwtAuthGuard + PlatformRolesGuard('superadmin')). Experts and AMs must never see other clients' raw outbound payloads (dlq-admin.controller.ts). Any new DLQ list endpoint must keep this scope.