Supabase Queues in Production: DLQs and Retries with pgmq
How to add dead-letter queues, retry limits, and poison message handling to Supabase Queues (pgmq) in production, with working SQL.
Supabase Queues offers a durable Postgres-native message queue via the pgmq extension, but the official docs stop at the basic send/read/archive flow. In production, pgmq has no built-in dead-letter queue, no retry limit, and no failure alerting — a message that always fails will be retried forever, silently consuming worker capacity.
This piece shows how to close that gap using pgmq's own read_ct column as a free retry counter, diverting messages that exceed a configurable retry budget into a second queue acting as a DLQ, and polling pgmq.metrics() to detect stuck queues. A wrapper SQL function intercepts pgmq.read(), automatically routing poison messages to the dead-letter queue while restricting execution to the service_role.
Compared to standing up a separate queue service like Redis+BullMQ or SQS, keeping jobs inside Postgres means messages live alongside application data, enqueues can join existing transactions, and deployment is just a migration file. The tradeoff is that operational conveniences taken for granted in managed queues — retry ceilings, dead-lettering, alerting — must be built by hand. For engineers running background jobs on Supabase, this covers the concrete failure modes and tested fixes the quickstart docs leave out.