At-least-once delivery
A received message is hidden while work is in progress. If the worker disappears, it becomes available again.
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.
Keep slow work from making people wait.
Unfinished jobs stay ready for another try.
Add help when demand climbs.
Know what is waiting, working, and done.
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.
A received message is hidden while work is in progress. If the worker disappears, it becomes available again.
Set visibility, retention, and receive limits per queue. Drop exhausted work or send it to a dead-letter queue.
Send many messages in one call and receive batches of up to ten when workers are ready for more.
PlainQ carries bytes as-is: JSON, protobuf, text, or a pointer to a larger object.
A worker can receive it now.
Hidden from other workers for 30 seconds.
The message is removed.
Attempt 2 becomes ready.
Keep the same durable queue semantics whether one worker group handles a job or several independent consumers need the same event.
stableAdd 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"stableEvery 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"}'Use the queue from code, a shell, a browser, or an agent. Each surface describes the same small model instead of inventing another one.
Readable output by default, stable JSON when a program is listening, and meaningful exit codes for automation.
plainq list -jsonUse the maintained Go client for queues, messages, and topics without hand-writing gRPC plumbing.
go get …/plainq-go-sdkThe schema is published in the Buf registry so other languages can generate a native client.
buf.build/plainq/schemaBrowse queues and metrics in the bundled web dashboard, or stay in the terminal with the built-in TUI.
plainq tuiInspect flags, effects, examples, and RPCs as JSON without starting or contacting a server.
plainq schema -jsonHealth checks, Prometheus metrics, structured logs, and queue dashboards are present from the first process.
/health · /metricsRun 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 optionsCopy 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.
# 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)"Keep slow, bursty, and failure-prone work outside the request path. Use familiar queue names instead of building a messaging platform first.
queue / media-jobsBuffer uploads, add workers when demand rises, and retry a conversion if a worker exits halfway through.
queue / webhook-inboxAccept an event quickly, process it off the request path, and keep transient downstream failures recoverable.
queue / order-eventsGive notifications, analytics, and audit processing their own queues and retry policies behind one topic.
queue / agent-runsQueue long-running tasks and inspect the system through stable JSON, explicit effects, and predictable exit codes.
Start with one process and one SQLite file. Keep the same queue API as the deployment grows around it.
# 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"