« All posts

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.

This synthesis was produced from its source by AI; there is no human editor or manual review step. How we work