Queen MQ
Quickstart

From nothing to a working pipeline in five minutes.

This is the shortest path. We'll start PostgreSQL + the Queen server in Docker, push a message with raw curl, then push and consume the same message with the client library of your choice. Every snippet on this page was tested against a live Queen server at http://localhost:6632.

1. Start PostgreSQL and Queen

Queen ships as a single Docker image. It needs a PostgreSQL instance, version 14 or newer is fine.

bash · start the stack
docker network create queen

docker run --name qpg --network queen \
  -e POSTGRES_PASSWORD=postgres \
  -p 5433:5432 -d postgres

# wait for Postgres to accept connections (a few seconds)
sleep 3

docker run -p 6632:6632 --network queen \
  -e PG_HOST=qpg \
  -e PG_PORT=5432 \
  -e PG_PASSWORD=postgres \
  -e NUM_WORKERS=2 \
  -e DB_POOL_SIZE=5 \
  -e SIDECAR_POOL_SIZE=30 \
  smartnessai/queen-mq:latest

Current stable release: 0.14.1. Pin with smartnessai/queen-mq:0.14.1 for reproducible deployments.

What just happened

Queen connects to PostgreSQL and applies its schema (queues, partitions, messages, DLQ, stats) on first start. The same binary serves the HTTP API on :6632 and the Vue 3 dashboard at /.

2. Verify it's up

bash
curl http://localhost:6632/health
# => {"database":"connected","queen":{"status":"active",...},"status":"healthy",...}

Open http://localhost:6632 in a browser to see the dashboard.

3. Your first push and pop

No SDK yet, let's prove the server works with raw HTTP. autoAck=true marks the message consumed on delivery, so this is a single round-trip.

bash · push a message
curl -X POST http://localhost:6632/api/v1/push \
  -H 'Content-Type: application/json' \
  -d '{"items":[{"queue":"demo","payload":{"hello":"world"}}]}'
bash · pop it back
curl 'http://localhost:6632/api/v1/pop/queue/demo?autoAck=true'
# => {"messages":[{"data":{"hello":"world"},"transactionId":"...","partitionId":"...",...}], ...}

Note we never created the demo queue explicitly, Queen creates queues on first push. To configure things like lease time, retry limit, or encryption you call POST /api/v1/configure first; see HTTP API.

4. Use a client library

Pick your stack. Each snippet creates a queue, pushes a message, then consumes it with auto-ack. All four were verified end-to-end against a live server.

// npm install queen-mq      (Node.js 22+)
import { Queen } from 'queen-mq'

const queen = new Queen('http://localhost:6632')

await queen.queue('tasks').create()

await queen.queue('tasks').push([
  { data: { task: 'send-email', to: 'alice@example.com' } }
])

await queen.queue('tasks').limit(1).consume(async (m) => {
  console.log('got:', m.data)
})

await queen.close()
Install: npm install queen-mq · requires Node 22+.
# pip install queen-mq      (Python 3.8+)
import asyncio
from queen import Queen

async def main():
    async with Queen('http://localhost:6632') as queen:
        await queen.queue('tasks').create()

        await queen.queue('tasks').push([
            {'data': {'task': 'send-email', 'to': 'alice@example.com'}}
        ])

        async def handler(m):
            print('got:', m['data'])

        await queen.queue('tasks').limit(1).consume(handler)

asyncio.run(main())
Install: pip install queen-mq · requires Python 3.8+.
// go get github.com/smartpricing/queen/client-go
package main

import (
    "context"
    "fmt"
    queen "github.com/smartpricing/queen/client-go"
)

func main() {
    ctx := context.Background()
    client, _ := queen.New("http://localhost:6632")
    defer client.Close(ctx)

    client.Queue("tasks").Create().Execute(ctx)

    client.Queue("tasks").
        Push([]interface{}{
            map[string]interface{}{"task": "send-email", "to": "alice@example.com"},
        }).Execute(ctx)

    client.Queue("tasks").Limit(1).
        Consume(ctx, func(c context.Context, m *queen.Message) error {
            fmt.Println("got:", m.Data)
            return nil
        }).Execute(ctx)
}
Install: go get github.com/smartpricing/queen/client-go · requires Go 1.24+.
// composer require smartpricing/queen-mq
use Queen\Queen;

$queen = new Queen('http://localhost:6632');

$queen->queue('tasks')->create()->execute();

$queen->queue('tasks')->push([
    ['data' => ['task' => 'send-email', 'to' => 'alice@example.com']]
])->execute();

$messages = $queen->queue('tasks')->batch(1)->pop();
foreach ($messages as $m) {
    echo "got: " . json_encode($m['data']) . "\n";
    $queen->ack($m, true);
}
Install: composer require smartpricing/queen-mq · PHP 8.3+. Works standalone or as a Laravel service provider.
#include "queen_client.hpp"
using namespace queen;

int main() {
    QueenClient client("http://localhost:6632");

    client.queue("tasks").create();

    client.queue("tasks").push({
        {{"data", {{"task", "send-email"}, {"to", "alice@example.com"}}}}
    });

    auto messages = client.queue("tasks").batch(1).wait(false).pop();
    if (!messages.empty()) {
        std::cout << "got: " << messages[0]["data"] << std::endl;
        client.ack(messages[0], true);
    }
    client.close();
}
Header-only. Drop clients/client-cpp/queen_client.hpp into your project. C++17, depends on cpp-httplib + nlohmann/json.
curl -X POST http://localhost:6632/api/v1/configure \
  -H 'Content-Type: application/json' \
  -d '{"queue":"tasks"}'

curl -X POST http://localhost:6632/api/v1/push \
  -H 'Content-Type: application/json' \
  -d '{"items":[{"queue":"tasks","payload":{"task":"send-email","to":"alice@example.com"}}]}'

curl 'http://localhost:6632/api/v1/pop/queue/tasks?autoAck=true&batch=1'
Plain HTTP, no client. Useful for shells, debugging, or integrating from any language.

5. The power example

Once you have the basics, the move you'll reach for most is the transactional pipeline: pop a message, transform it, then atomically ack the input and push the output to another queue. This is the pattern that makes multi-stage workflows correct.

// Pop one message from 'raw' and atomically:
//   1) ack it as completed
//   2) push a derived message into 'processed'
const [m] = await queen.queue('raw').batch(1).pop()

await queen
  .transaction()
  .ack(m)
  .queue('processed').push([
    { data: { id: m.data.id, doubled: m.data.value * 2 } }
  ])
  .commit()
[m] = await queen.queue('raw').batch(1).pop()

await (queen.transaction()
       .ack(m)
       .queue('processed').push([
           {'data': {'id': m['data']['id'], 'doubled': m['data']['value'] * 2}}
       ])
       .commit())
msgs, _ := client.Queue("raw").Batch(1).Pop(ctx)
m := msgs[0]

_, _ = client.Transaction().
    Ack(m, "completed", queen.AckOptions{}).
    Queue("processed").
    Push([]interface{}{
        map[string]interface{}{
            "id":      m.Data["id"],
            "doubled": m.Data["value"].(float64) * 2,
        },
    }).
    Commit(ctx)
curl -X POST http://localhost:6632/api/v1/transaction \
  -H 'Content-Type: application/json' \
  -d '{
    "operations": [
      {"type":"ack",  "transactionId":"<tx>", "partitionId":"<pid>", "status":"completed"},
      {"type":"push", "items":[{"queue":"processed","payload":{"doubled":true}}]}
    ]
  }'
Exactly-once

The transaction is a single PostgreSQL BEGIN…COMMIT. If the push fails the ack rolls back; the message reappears for retry. If the ack succeeds the push is durable. There is no "in-between" state.

6. Where to go next

Read

Concepts

Partitions, consumer groups, leases, retries, DLQ, failover, one paragraph each.

Build

Client patterns

Side-by-side recipes for batching, long polling, lease renewal, transactions.

Operate

Server setup

Env vars, JWT auth, Kubernetes, multi-instance UDP sync.

Watch

Dashboard tour

Real-time queue metrics, message browser, trace timelines, DLQ management.