Durable jobs, automatic retries, dead-letter queues, cron schedules, DAG workflows
and real-time streaming — over REST and gRPC.
Website · Docs · Live demo · Dashboard
Your app has work that shouldn't happen during a request: sending the welcome email, fulfilling a Stripe order, resizing an upload, generating a report. That work fails in ways a request cannot recover from — the mail provider is down, the payment API times out, the box restarts mid-job.
Spooled is where that work waits. You enqueue a job over REST or gRPC, your own workers claim it and run it, and Spooled handles what happens when it doesn't go cleanly: retry with exponential backoff, park it in a dead-letter queue after the last attempt, and stream every state change so you can watch it happen.
Jobs live in PostgreSQL, so they survive a restart and you can query them with SQL you already know. Your code runs on your infrastructure — Spooled stores and schedules the work, it never executes it.
your app spooled your workers
──────── ─────── ────────────
enqueue ──────────▶ ┌──────────────────────┐ ◀────────── claim
│ queue (Postgres) │
webhook ──────────▶ │ ● ● ● ● ● │ ──────────▶ run your code
(Stripe, GitHub) │ │
│ retries · priority │ ◀────────── complete / fail
cron ──────────────▶ │ schedules · DLQ │
└─────────┬────────────┘
│
state streams back
(WebSocket / SSE)
See it running → — SpriteForge builds an animated sprite from parallel frame jobs, with retries and live events you can watch. It is a real workload against production, not a mock.
Get a key at dashboard.spooled.cloud, then:
Node.js / TypeScript
npm install @spooled/sdkimport { SpooledClient, SpooledWorker } from "@spooled/sdk";
const client = new SpooledClient({ apiKey: process.env.SPOOLED_API_KEY! });
// Enqueue — returns as soon as the job is durable.
await client.jobs.create({
queueName: "emails",
payload: { to: "user@example.com", subject: "Welcome!" },
idempotencyKey: `welcome:${user.id}`, // retrying this call won't double-send
});
// Process — runs wherever you run it.
new SpooledWorker({
apiKey: process.env.SPOOLED_API_KEY!,
queueName: "emails",
handler: async (job) => {
await sendEmail(job.payload.to, job.payload.subject);
return { sent: true };
},
}).start();Python
pip install spooledimport os
from spooled import SpooledClient, SpooledWorker
client = SpooledClient(api_key=os.environ["SPOOLED_API_KEY"])
client.jobs.create({
"queue_name": "emails",
"payload": {"to": "user@example.com", "subject": "Welcome!"},
"idempotency_key": f"welcome:{user.id}",
})
def handle(job):
send_email(job.payload["to"], job.payload["subject"])
return {"sent": True}
SpooledWorker(
api_key=os.environ["SPOOLED_API_KEY"],
queue_name="emails",
handler=handle,
).start()Go
go get github.com/spooled-cloud/spooled-sdk-goclient := spooled.NewClient(spooled.WithAPIKey(os.Getenv("SPOOLED_API_KEY")))
client.Jobs().Create(ctx, &resources.CreateJobRequest{
QueueName: "emails",
Payload: map[string]any{"to": "user@example.com", "subject": "Welcome!"},
IdempotencyKey: "welcome:" + user.ID,
})
w := spooled.NewSpooledWorker(
os.Getenv("SPOOLED_API_KEY"), "emails",
func(ctx context.Context, job *worker.Job) (map[string]any, error) {
return map[string]any{"sent": true}, sendEmail(job.Payload)
},
)
w.Start(ctx)PHP
composer require spooled-cloud/spooleduse Spooled\SpooledClient;
use Spooled\Config\ClientOptions;
$client = new SpooledClient(new ClientOptions(apiKey: getenv('SPOOLED_API_KEY')));
$client->jobs->create([
'queue_name' => 'emails',
'payload' => ['to' => 'user@example.com', 'subject' => 'Welcome!'],
'idempotency_key' => "welcome:{$user->id}",
]);REST (any language)
curl -X POST https://api.spooled.cloud/api/v1/jobs \
-H "Authorization: Bearer $SPOOLED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"queue_name": "emails",
"payload": { "to": "user@example.com", "subject": "Welcome!" },
"idempotency_key": "welcome:123"
}'| Durable queues | Jobs in PostgreSQL, with priority (0–100) and FIFO within a priority |
| Automatic retries | Exponential backoff, configurable max attempts |
| Dead-letter queue | Exhausted jobs are kept for inspection and replay, not dropped |
| Idempotency keys | The same key enqueues one job, however many times you call |
| Cron schedules | 5-field cron, or 6-field with a leading seconds column, with timezones |
| Workflows | DAG dependencies — run B and C after A completes |
| Incoming webhooks | Endpoints that turn a Stripe or GitHub delivery into a job |
| Outgoing webhooks | Delivery on job events, HMAC-signed when you set a secret |
| Real-time | WebSocket and SSE streams of job, queue and worker state |
| gRPC streaming | Bidirectional ProcessJobs for workers that pull continuously |
Each of these is its own repository with its own releases — they are developed side by side but versioned independently. (The marketing site and documentation live in a private repository; the docs themselves are public at spooled.cloud/docs.)
| Repository | What it is |
|---|---|
| spooled-backend | Rust API server — REST, gRPC, WebSocket, scheduler, workers |
| spooled-dashboard | Job, queue and worker monitoring UI |
| spooled-sdk-nodejs | Node.js / TypeScript client |
| spooled-sdk-python | Python client |
| spooled-sdk-go | Go client |
| spooled-sdk-php | PHP client |
| spooled-example-spriteforge | The live demo — a full app built on Spooled |
API keys are issued with an sp_live_ or sp_test_ prefix. Keys with the older
sk_ prefix are still accepted, so existing integrations keep working.
Authorization: Bearer sp_live_... # RESTx-api-key: sp_live_... # gRPC metadata
A key is shown once, at creation. Keys can be scoped to specific queues, and a scoped key cannot mint a broader one.
Job payloads are stored as plain JSONB and are not encrypted at the application layer. Keep secrets out of payloads — pass a reference and resolve it in your worker. See Security.
| REST API | https://api.spooled.cloud |
| gRPC | grpc.spooled.cloud:443 (TLS) |
| Dashboard | https://dashboard.spooled.cloud |
| Docs | https://spooled.cloud/docs |
| Status | https://spooled.cloud/status |
The core is Apache-2.0 and runs on your own PostgreSQL. You need Postgres and Redis; everything else is the one Rust binary.
git clone https://github.com/Spooled-Cloud/spooled-backend.git
cd spooled-backend
docker compose up -d postgres redis
cargo run
# REST on :8080, gRPC on :50051Full guide: spooled.cloud/docs/deployment. There is no feature difference between self-hosted and hosted — the hosted service is the same code, operated for you.
Issues and pull requests are welcome on any of the repositories above. Each
carries its own CONTRIBUTING.md with its test and release process.
When you change a public contract, the SDK test-local.* scripts and
spooled-frontend/src/lib/snippets.ts are the places docs and examples are
generated from — keeping them in step is what stops the docs drifting.
The open-source code is Apache-2.0, copyright Yevhen Salitrynskyi — see LICENSE.
Spooled Cloud, the hosted service, is operated by YS Progress Inc. (Ontario, Canada) under its own Terms and Privacy Policy. You can self-host the open-source core with no relationship to the hosted service.
⭐ Star a repo if this saved you a queue you didn't want to build · Sponsor

