A message queue that moves out of your way

Spooler is a fully managed message queue.
Messages go in, messages come out. Leases, retries, and recovery are already in between.

How it works

Create queue

import "github.com/spoolersh/spooler-go"

// A client for the public API.
c := spooler.Client{
	APIKey: "...",
}
// Every account has a spool named default; queues live there.
// Create one once: it comes with the server's default settings.
err := c.CreateQueue(ctx, spooler.CreateQueueRequest{
	Spool: "default",
	Queue: "orders",
})
if errors.Is(err, spooler.ErrQueueExists) {
	err = nil
}
if err != nil {
	// Handle the error.
}

Send message

import "github.com/spoolersh/spooler-go"

// A client for the public API.
c := spooler.Client{
	APIKey: "...",
}
// The body is the message: bytes in, bytes out, no envelope.
// Send returns once it is stored; the id comes back for logs.
res, err := c.Send(ctx, spooler.SendRequest{
	Spool: "default",
	Queue: "orders",
	Data:  []byte("hello, spooler!"),
})
if err != nil {
	// Handle the error.
}
fmt.Println(res.ID) // 1-1

Receive message

import "github.com/spoolersh/spooler-go"

// A client for the public API.
c := spooler.Client{
	APIKey: "...",
}
// A receive does not delete the message: it leases it, and no other
// receiver gets it while the lease is live; Ack presents it back.
msg, ok, err := c.Recv(ctx, spooler.RecvRequest{
	Spool: "default",
	Queue: "orders",
})
if err != nil {
	// Handle the error.
}
if !ok {
	// The wait elapsed with nothing to receive.
}
fmt.Printf("%q", msg.Data) // "hello, spooler!"

Ack the message

import "github.com/spoolersh/spooler-go"

// A client for the public API.
c := spooler.Client{
	APIKey: "...",
}
// Present the lease back: the message is acknowledged and removed
// from the queue. That is the whole loop: send, receive, ack.
err := c.Ack(ctx, spooler.AckRequest{
	Spool: "default",
	Lease: msg.Lease,
})
if err != nil {
	// Handle the error.
}

Atomic ack-and-send

import "github.com/spoolersh/spooler-go"

// A pipeline stage settles both sides in one call: AckAndSend acks
// the delivery and appends the next message, atomically.

// A client for the public API.
c := spooler.Client{
	APIKey: "...",
}
// The lease names the message acked; Queue names where the new one
// lands (create "next" first, as with "orders").
next, err := c.AckAndSend(ctx, spooler.AckAndSendRequest{
	Spool: "default",
	Lease: msg.Lease,
	Queue: "next",
	Data:  []byte("hello, next stage"),
})
if err != nil {
	// Handle the error.
}
fmt.Println(next.ID) // 1-3

Why use Spooler?

  • Durability

    A send answers only after the message is written and synced to disk, so it survives a crash or restart. An ack is durable the same way: that message will not come back.

  • Leases

    A receive leases a message: it stays hidden while you process it and comes back if you never ack. Renew for long jobs. A consumer that dies mid-work costs a redelivery, not a message.

  • Failed messages

    A failing message retries and fails alone while everything behind it flows. Past the queue's retry limit, or the moment your handler fails it deliberately, it rests as failed, intact: a state of the same message, not a place it moves to. List it, peek at it, recover it, or discard it.

  • Atomic ack-and-send

    A stage that consumes from one queue and produces into the next settles both in one call: the input acked and the output appended together. A retry of a call that already committed appends nothing.

  • Deduplication

    Give a queue a dedup window and each send a key: a repeat with the same key inside the window appends nothing, so a producer retrying after a dropped connection is safe.

  • Rate limiting

    A queue can cap how fast it hands out messages: a sustained rate and a burst, enforced on the server and shared by every consumer. Pace a stage to what a downstream API tolerates, without a limiter in each worker.

Pricing

A spool has a storage cap; at the cap, sends are refused until consumers drain it. A stalled consumer is a full spool, not a surprise invoice.

  • Solo

    £29 / month

    60 days trial — no card required

    • 1 spool
    • 250 req/s
    • 7 days retention

    Per spool:

    • 1,000 queues
    • 100,000 stored messages
    • 2 GiB stored data
    • 64 KiB per message
    • 1 replica
  • Team

    £149 / month

    45 days trial — card required

    • 2 spools
    • 500 req/s
    • 14 days retention

    Per spool:

    • 10,000 queues
    • 250,000 stored messages
    • 4 GiB stored data
    • 64 KiB per message
    • 2 replicas
  • Business

    £299 / month

    30 days trial — card required

    • 3 spools
    • 1,000 req/s
    • 30 days retention

    Per spool:

    • 100,000 queues
    • 500,000 stored messages
    • 8 GiB stored data
    • 64 KiB per message
    • 3 replicas