Reliable queues.One simple binary.

PlainQ gives you durable work queues, retries, dead-lettering, a typed gRPC API, and an admin UI—without a broker fleet. Start with SQLite; add PostgreSQL or Raft replication when your deployment grows.

Stay responsive

Keep slow work from making people wait.

Finish the work

Unfinished jobs stay ready for another try.

Handle the rush

Add help when demand climbs.

See it clearly

Know what is waiting, working, and done.

Workers fail. Messages wait.That’s normal.

PlainQ keeps unfinished work available without forcing your application to coordinate every worker. The delivery model is small enough to explain—and complete enough to trust.

At-least-once delivery

A received message is hidden while work is in progress. If the worker disappears, it becomes available again.

Retries with an end

Set visibility, retention, and receive limits per queue. Drop exhausted work or send it to a dead-letter queue.

Efficient batches

Send many messages in one call and receive batches of up to ten when workers are ready for more.

Payloads stay yours

PlainQ carries bytes as-is: JSON, protobuf, text, or a pointer to a larger object.

Message lifecycle
01
queuedjob_4021 is ready

A worker can receive it now.

02
in flightWorker 01 receives it

Hidden from other workers for 30 seconds.

03
acknowledgedWork finished

The message is removed.

timeoutWorker disappeared

Attempt 2 becomes ready.

Queue state persisted

Queues for work.
Topics for fan-out.

Keep the same durable queue semantics whether one worker group handles a job or several independent consumers need the same event.

Work queuestable

One message. One worker group.

Add consumers without coordinating them. Each message goes to one worker and returns to the queue if it is not acknowledged.

plainq send -message='{"job":"resize"}' "$QID"
Topic fan-outstable

Publish once. Deliver independently.

Every subscribed queue gets its own durable copy, with its own retries, retention, and dead-letter policy.

plainq topic publish "$TOPIC_ID" -message='{"event":"order.created"}'

One binary.
No single interface.

Use the queue from code, a shell, a browser, or an agent. Each surface describes the same small model instead of inventing another one.

CLI

Friendly to humans and scripts

Readable output by default, stable JSON when a program is listening, and meaningful exit codes for automation.

plainq list -json
Go SDK

A typed client for every RPC

Use the maintained Go client for queues, messages, and topics without hand-writing gRPC plumbing.

go get …/plainq-go-sdk
gRPC + Buf

Generate the client you need

The schema is published in the Buf registry so other languages can generate a native client.

buf.build/plainq/schema
Houston + TUI

Operate it without another service

Browse queues and metrics in the bundled web dashboard, or stay in the terminal with the built-in TUI.

plainq tui
Agent-native

The command surface explains itself

Inspect flags, effects, examples, and RPCs as JSON without starting or contacting a server.

plainq schema -json
Operations

The basics ship in the binary

Health checks, Prometheus metrics, structured logs, and queue dashboards are present from the first process.

/health · /metrics
Storage topology
QPlainQqueue API
SQLiteone node · one local file
Tursohosted SQLite · one node
PostgreSQLshared backend · many instances
Need machine-level availability?Replicate SQLite state with Raft →

Start small. Add only what you need.

Run an embedded SQLite file on one host. Choose Turso for hosted SQLite, PostgreSQL for a shared backend, or Raft when replicated SQLite availability matters.

Compare storage options

Deploy like the rest of your stack.

Copy the binary to a host, build the distroless non-root container, or install the Helm chart. The operator can manage servers, queues, topics, and accounts declaratively on Kubernetes.

  • single process + local file
  • Docker + persistent volume
  • Helm + Kubernetes operator
deploy.sh
# Build an image and run one durable node$ make docker IMAGE=plainq VERSION=dev$ docker run --rm -p 8080:8080 -p 8081:8081 \    -v plainq-data:/data plainq:dev serve \    -storage.path=/data/plainq.db \    -auth.jwt.secret="$(openssl rand -hex 32)"# Or install the included Helm chart$ helm install plainq deploy/helm/plainq \    --set auth.jwtSecret="$(openssl rand -hex 32)"

Built for the work your app already has.

Keep slow, bursty, and failure-prone work outside the request path. Use familiar queue names instead of building a messaging platform first.

queue / media-jobs

Image and video processing

Buffer uploads, add workers when demand rises, and retry a conversion if a worker exits halfway through.

queue / webhook-inbox

Webhook buffering

Accept an event quickly, process it off the request path, and keep transient downstream failures recoverable.

queue / order-events

Independent fan-out

Give notifications, analytics, and audit processing their own queues and retry policies behind one topic.

queue / agent-runs

Automation and agent work

Queue long-running tasks and inspect the system through stable JSON, explicit effects, and predictable exit codes.

Run the queue.
Skip the broker fleet.

Start with one process and one SQLite file. Keep the same queue API as the deployment grows around it.

quickstart
# Build and start PlainQ (SQLite by default)$ make build$ ./plainq serve --auth.jwt.secret="$(openssl rand -hex 32)"# In another terminal: create → send → receive$ QID=$(./plainq create jobs)$ ./plainq send -message='{"job":"resize"}' "$QID"$ ./plainq receive -ack "$QID"