PGMQ: When Postgres Is All the Message Queue You Need
PGMQ turns the Postgres you already run into a real message queue — visibility timeouts, transactional enqueue, dead letters.
For Technical Teams

On this page
If Postgres is already running, your first message queue should probably live inside it too. PGMQ turns Postgres tables into a real queue — visibility timeouts, transactional enqueue, dead-letter handling — with no new broker and no second system to maintain.
At some point in every backend's life, a job needs to happen later or elsewhere: send the invoice email, recompute the report, fire the webhook. The reflex answer is "add a message queue," and the reflex implementation is Kafka or RabbitMQ. On a large share of the systems I build and review, that reflex bolts a distributed system onto a product whose database could already do the job.
A queue that lives inside your database
PGMQ is a Postgres extension that implements a message queue as tables
plus a SQL API: pgmq.send(), pgmq.read(), pgmq.delete(),
pgmq.archive(). There is no broker process, no new port, no new
deployment artifact.
Client libraries exist where you want the ergonomics — in Node, pgmq-js
wraps queue creation, send, and read in a few calls. But everything is
ultimately SQL against the database you already operate.
That framing matters more than any feature list: the queue inherits everything your Postgres already has. Backups, replication, monitoring, access control, point-in-time recovery — none of it has to be built a second time for a second system.
Visibility timeout, not locks
Think of a restaurant marking a table "being served" the moment a waiter takes the order. Other waiters can't grab that table — but if the first waiter vanishes without finishing, the table opens back up and someone else can take it. Visibility timeout works the same way.
PGMQ's delivery model is the one SQS made famous. A consumer reads a message with a visibility timeout — say 30 seconds. The message doesn't leave the queue; it becomes invisible to other consumers for that window. Finish the work and delete it to remove it, or archive it to retain it outside the active queue. Crash mid-job and do nothing, and the message simply reappears when the timeout expires, ready for the next consumer.
That gives you at-least-once delivery with crash recovery and no stuck locks. It also puts one obligation on you: consumers must be idempotent, because "at least once" occasionally means twice. Design every handler so a repeat delivery is harmless, and test retry, timeout, and uncertain-result handling.
The killer feature: transactional enqueue
An important benefit over a separate broker is enqueueing a message in the same commit as your business data.
With an external broker there are two systems, so there are two failure modes. Commit the order but fail to publish the event, and downstream never hears about it. Publish the event but roll back the order, and downstream processes something that doesn't exist.
The industry's answer is the outbox pattern — write to an outbox table transactionally, relay from it later — which is a polite way of saying "build half a queue inside Postgres anyway." With PGMQ the queue is a table in the same database:
BEGIN;
INSERT INTO orders (...) VALUES (...);
SELECT pgmq.send('notifications', '{"order_id": 118}');
COMMIT;
One commit. Either the order and its message both exist, or neither does. In an operations backend I built for a logistics company, this single property eliminated a reconciliation job whose only purpose had been catching dual-write drift between the database and the broker.
The operational weight you skip
Kafka is brokers, a controller quorum, partitions, consumer groups, retention tuning, and a dashboard you check when rebalancing goes sideways. RabbitMQ is milder, but it's still another stateful cluster with its own upgrade path and failure modes. Both are worth that cost when you genuinely need them. Neither is ever free.
A queue in Postgres is one more schema. It's backed up by the backup you already take, monitored by the monitoring you already run, and upgraded through explicit extension updates. For a small team, that difference decides whether the on-call rotation is survivable.
Throughput ceilings, and when to graduate
Honest numbers: on modest hardware I've benchmarked PGMQ at single-digit millisecond read latencies and thousands of messages per second — several orders of magnitude above what a typical line-of-business backend produces.
The ceiling is real, though. The polling architecture described here has consumers read on a schedule, hot queue tables need vacuum attention, and everything shares I/O with your application.
Graduate when the workload changes shape, not just size: sustained tens of thousands of messages per second, replay of history, or many independent consumer groups reading the same stream. Those are event-log semantics, and Kafka is an event log. PGMQ is a work queue. Different tools — most backends only ever need the second one.
Dead letters
Poison messages — the ones that crash their consumer every time — must
not circulate forever. PGMQ tracks a read count per message, so
dead-letter handling is a few lines of SQL: when read_ct passes your
threshold, move it to a dead-letter queue and remove or archive the original in the same database transaction; alert on dead-letter depth. Archive successfully processed messages when you need that history; deleted messages do not remain in the archive.
The takeaway
You get visibility-timeout semantics, enqueueing in the same PostgreSQL transaction as business rows, and operations built on PostgreSQL, with queue-specific retention, vacuum, extension updates, and shared-load monitoring. Move to Kafka when you need an event log, not because a diagram looked more impressive with one.
This is the same philosophy as grouping across time zones in PostgreSQL: push the work into the database that's already there. Many of the IoT backends I build run their job queues exactly this way.
Related Posts
Need help applying this approach?
Business Operations System Development
Bring orders, customer data, and staff workflows into a system shaped around your business, with clear permissions and reports.
Explore related servicesDescribe the operational problem you are facing so we can discuss a suitable next step.