Open source · Apache 2.0 · written in Go

Truly simple queue service.

PlainQ is a small, self-contained queue server. One binary gives you a gRPC API, a CLI, a terminal UI, and a built-in admin dashboard — backed by embedded SQLite or PostgreSQL when you need to scale out.

quickstart
# Build, then start the server (SQLite by default)$ make build$ ./plainq serve --auth.jwt.secret="$(openssl rand -hex 32)"# In another shell — create, send, receive$ QID=$(./plainq create my-queue)$ ./plainq send -message='hello, plainq' "$QID"$ ./plainq receive -ack "$QID"
1
binary — no broker fleet
8
gRPC RPCs, fully typed
2
storage backends (SQLite · Postgres)
0
external dependencies to run

Everything in the box

PlainQ is intentionally boring on the inside, so the surface stays small and the operations stay quiet.

One binary, no broker fleet

Run ./plainq serve and you have a queue. No Zookeeper, no separate broker cluster, no operational sprawl.

gRPC API + CLI + TUI

The same surface, scripted or interactive. Eight typed RPCs, a full CLI, and a rich Bubble Tea terminal dashboard.

Houston admin UI

An Astro + React dashboard for queues, accounts, RBAC, and metrics — served straight from the same binary.

Pick your storage

Embedded SQLite by default — Litestream-friendly for cheap replication. Switch to PostgreSQL for a shared backend.

Auth that's actually built in

JWT sessions, refresh tokens, RBAC, and OAuth/OIDC hooks (Kinde, Auth0, Okta, WorkOS) ship with the server.

Built for humans and agents

-json everywhere and schema introspection make the CLI a first-class tool for scripts and AI agents alike.

One surface, scripted or wired

Drive queues from the terminal, or generate a client SDK straight from the published protobuf schema. Same semantics either way.

CLI

plainq
# Flags go BEFORE the positional queue id$ plainq create \    -visibility-timeout=30 \    -max-receive-attempts=5 \    -drop-policy=dead-letter \    my-queue$ plainq send -message='{"job":"resize"}' "$QID"$ plainq receive -batch=10 -ack "$QID"# Machine-readable output for scripts and agents$ plainq list -json | jq '.queues[].queue_name'

gRPC

schema.proto
// The wire API is published to the Buf Schema Registry://   buf.build/plainq/schemaservice PlainQ {  rpc ListQueues(ListQueuesRequest)   returns (...);  rpc DescribeQueue(DescribeRequest)  returns (...);  rpc CreateQueue(CreateQueueRequest) returns (...);  rpc PurgeQueue(PurgeQueueRequest)   returns (...);  rpc DeleteQueue(DeleteQueueRequest) returns (...);  rpc Send(SendRequest)               returns (...);  rpc Receive(ReceiveRequest)         returns (...);  rpc Delete(DeleteRequest)           returns (...);}

Ship it anywhere

From a laptop to a cluster, the same binary follows you. Pick the smallest thing that works.

Single binary

make build produces ./plainq. Copy it to a box, run serve, and you are done — SQLite lives in one file.

Docker

An optimized multi-stage image: Bun builds Houston, Go builds a static binary, shipped on distroless:nonroot.

Kubernetes (Helm)

A StatefulSet + PVC for SQLite, or a Deployment + HPA when you point it at PostgreSQL. JWT secret from a Secret.

docker
# Distroless, static binary, nonroot$ 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)"
helm
# Production-grade chart with StatefulSet + PVC$ helm install plainq deploy/helm/plainq \    --set auth.jwtSecret="$(openssl rand -hex 32)"

A queue, in under a minute.

From a fresh clone to a message round-trip. No services to provision, no YAML to write first.

get started
$ make build && ./plainq serve \    --auth.jwt.secret="$(openssl rand -hex 32)"