Queue and Pub/Sub.For humans and agents.

Durable queues and Pub/Sub, hosted for your applications. Bring people and agents into one organization, give each team its own workspace, and let PlainQ run the infrastructure.

FIG. 01 / WORK QUEUEFrom send to acknowledgement
PLAINQ / 01
03 ready00 in flight
01 Send02 Queue03 Process & acknowledge

A visitor carrying a letter joins the line. Each visitor represents one queued message.

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.

FIG. 02 / SHARED QUEUEOne recipient at a time
PLAINQ / 02
01 ready00 in flight
MESSAGE 01 WAITING

Three clerks share one waiting line. There is still only one visitor carrying one message.

POST /queue/{queueId}/messages · Bearer {teamApiKey}
Topic fan-outstable v1

Publish once. Deliver independently.

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

FIG. 03 / TOPIC FAN-OUTPublish one message
PLAINQ / 03
00 published00 copies
ORDER.CREATED CHOOSE A QUEUE TO RETRY

Publish order.created once. Three distinct visitors collect matching copies of the same letter.

POST /queue/topics/{topicId}/publish · Bearer {teamApiKey}

Your queues.
Your whole team.

Use PlainQ from code, a browser, or an agent. Bring people into an organization, keep work inside teams, and connect through the same small queue model.

HTTP API

Friendly to code and agents

Send, receive, and acknowledge messages through a simple JSON API. Connect with the tools your application already uses.

Cloud dashboard

See the work in one place

Create queues, browse messages, and connect Pub/Sub topics in a familiar PlainQ dashboard, organized around your teams.

Organizations

One workspace for your company

Bring your people together in an organization. Choose one plan and share its resource limits across your teams.

Teams

Keep each team's work separate

Give each team its own queues and topics. Assign organization members to the teams they work with.

API keys

Connect with the right access

Issue API keys for a specific team. Applications can use that team's resources without reaching another team's queues.

Authorization: Bearer {teamApiKey}
Managed infrastructure

Leave the servers to PlainQ

PlainQ prepares the queue infrastructure behind your organization based on its plan. Your team works with queues and topics.

your plan → your workspace
FIG. 04 / YOUR ORGANIZATIONAcme · example workspace
ACME / PLAINQ
One plan for the organization
Separate workspaces for each team

Engineering has its own queues, topics and API keys. The organization shares one plan.

Start small. Make room for your team.

Create an organization, choose a plan, and add your teams. Each team gets its own queues, topics, and API keys. PlainQ prepares the infrastructure behind your workspace.

Create your organization

Send your first message from Go.

Create a queue and send bytes with the official Go SDK. SetPLAINQ_ADDR to your native PlainQ gRPC address. Cloud team endpoints currently use HTTP; this SDK example connects directly to a PlainQ server over gRPC.

go get github.com/marsolab/plainq-go-sdk
Go SDK documentation →
send.go · gRPC
package main

import (
    "context"
    "log"
    "os"
    "time"

    plainq "github.com/marsolab/plainq-go-sdk"
    v1 "github.com/marsolab/plainq-go-sdk/v1"
)

func main() {
    client, err := plainq.New(os.Getenv("PLAINQ_ADDR"))
    if err != nil { log.Fatal(err) }
    defer client.Close()

    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancel()

    queue, err := client.CreateQueue(ctx, &v1.CreateQueueRequest{
        QueueName: "jobs",
    })
    if err != nil { log.Fatal(err) }

    sent, err := client.Send(ctx, &v1.SendRequest{
        QueueId: queue.GetQueueId(),
        Messages: []*v1.SendMessage{
            {Body: []byte("hello, plainq")},
        },
    })
    if err != nil { log.Fatal(err) }
    if len(sent.GetMessageIds()) != 1 { log.Fatal("message was not enqueued") }
    log.Printf("queue ID: %s", queue.GetQueueId())
}

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 their progress through the same team-scoped JSON API your application uses.

One organization.
Room to grow.

Queues, Pub/Sub, and team workspaces in every plan. Choose the room your organization needs, and let PlainQ handle the infrastructure.

Starter

For your first application.

$19/ month

Per organization · USD

  • Teams2
  • Queues10
  • Pub/Sub topics5
  • Team-scoped API keys
  • Managed infrastructure
Get started

Team

For teams building together.

$49/ month

Per organization · USD

  • Teams10
  • Queues100
  • Pub/Sub topics50
  • Team-scoped API keys
  • Managed infrastructure
Get started

Business

For more teams and workloads.

$149/ month

Per organization · USD

  • Teams50
  • Queues500
  • Pub/Sub topics250
  • Team-scoped API keys
  • Managed infrastructure
Get started

Run the queue.
Skip the broker fleet.

Create your organization, connect your application, and put your queues to work. PlainQ takes care of the infrastructure underneath.

Go SDK · gRPC: receive and acknowledge a message from a native PlainQ server. Use the same PLAINQ_ADDR as the send example and set PLAINQ_QUEUE_ID to its printed queue ID. For Cloud HTTP connections, follow the Cloud quickstart.

receive.go · gRPC
package main

import (
    "context"
    "log"
    "os"
    "time"

    plainq "github.com/marsolab/plainq-go-sdk"
    v1 "github.com/marsolab/plainq-go-sdk/v1"
)

func main() {
    client, err := plainq.New(os.Getenv("PLAINQ_ADDR"))
    if err != nil { log.Fatal(err) }
    defer client.Close()

    ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
    defer cancel()
    queueID := os.Getenv("PLAINQ_QUEUE_ID")

    received, err := client.Receive(ctx, &v1.ReceiveRequest{
        QueueId: queueID, BatchSize: 1, WaitTimeSeconds: 5,
    })
    if err != nil { log.Fatal(err) }

    for _, message := range received.GetMessages() {
        log.Printf("received: %s", message.GetBody())
        ack, err := client.Delete(ctx, &v1.DeleteRequest{
            QueueId: queueID, MessageIds: []string{message.GetId()},
        })
        if err != nil { log.Fatal(err) }
        if len(ack.GetFailed()) > 0 { log.Fatal(ack.GetFailed()) }
    }
}