Quickstart
The goal: get one failed unit of work retrying — and, when it exhausts its policy, landing in a dead-letter queue you can inspect and redrive. Pick your host below. All three share the same storage, envelope, and outcome contract.
Prerequisites
Section titled “Prerequisites”- A reachable PostgreSQL 14+ instance (the v1 reference backend).
- One of: Go 1.22+, Java 17+, or Docker.
Path A — Go SDK
Section titled “Path A — Go SDK”import ( "context" "time"
"github.com/srjn45/rdq/sdk-go" "github.com/srjn45/rdq/sdk-go/submit" "github.com/srjn45/rdq/storage/postgres" "github.com/srjn45/rdq/core/envelope")
// 1. Open storage and apply migrations once.db, _ := postgres.Open("postgres://user:pass@localhost/mydb")_ = postgres.Migrate(ctx, db)store := postgres.New(db)
// 2. Register your handler under a stable name.rdq.Register("charge-payment", func(ctx context.Context, t envelope.Envelope) error { // decode t.Payload(), do real work … // return nil → SUCCEEDED; return err → retried per policy return nil})
// 3. Submit a task from the producer side.env, _ := submit.Submit("payments.charge", "charge-payment", []byte(`{"order_id":"42"}`))_ = store.Enqueue(ctx, env)
// 4. Run the worker (blocks until ctx is cancelled).worker, _ := rdq.NewWorker(store, rdq.WithQueue("payments.charge", rdq.MaxAttempts(5), rdq.BackoffExponential(time.Second, 2.0, 5*time.Minute),))worker.Run(ctx)Full detail — outcome classification, the submit-only sub-package, graceful shutdown — is in the Go SDK guide.
Path B — Java SDK
Section titled “Path B — Java SDK”Storage store = PostgresStorage.open(dataSource);
HandlerRegistry registry = new HandlerRegistry();registry.register("charge-payment", new Handler() { @Override public String version() { return "v1"; } @Override public void handle(Envelope task) throws Exception { // decode task.payload(), do real work … }});
QueueSpec spec = QueueSpec.builder("payments.charge") .maxAttempts(5) .backoff(Backoff.exponential(Duration.ofSeconds(1), 2.0, Duration.ofMinutes(5))) .build();
Worker.builder(store, registry).addQueue(spec).build().run();A normal return is a success; a thrown exception is a failure, classified retryable or permanent by the queue’s error lists. More in the Java SDK guide.
Path C — rdq-server (any language)
Section titled “Path C — rdq-server (any language)”Start the hub against your storage:
docker run -e RDQ_DSN=postgres://user:pass@host/db \ -p 8080:8080 ghcr.io/srjn45/rdq-server:latestSubmit a task over REST (payload is base64-encoded opaque bytes):
curl -X POST localhost:8080/v1/queues/payments.charge/tasks \ -H 'content-type: application/json' -d '{ "handler_ref": "charge-payment", "payload": "eyJvcmRlcl9pZCI6IjQyIn0="}'Register a per-queue callback (rdq-server invokes it to execute the task), and failures flow
through the same retry/DLQ path. See the rdq-server guide and the
Server API reference.
Inspect and redrive
Section titled “Inspect and redrive”However you submitted it, when a task exhausts its policy it lands in the DLQ with its full attempt history. Inspect and replay with the CLI:
# what's dead, and whyrdq dlq list --queue payments.charge
# read one task's complete failure historyrdq dlq get 01J8Z8M4Q0P9K3X2C7B1A6D5E4
# fix shipped — redrive everything that timed out after 14:00rdq dlq redrive --queue payments.charge --error TimeoutException --since 14:00Bulk redrive resets the retry policy and re-enqueues the matching tasks; every mutation is audit-logged.
What just happened
Section titled “What just happened”- You submitted
(queue, handler_ref, payload)to durable storage. - A stateless worker atomically claimed the due task under a lease, invoked the handler, and recorded the attempt.
- On failure it backed off and rescheduled; on exhaustion it dead-lettered with full history.
- You browsed the DLQ and redrove after the fix — cross-host, cross-language.
Next steps
Section titled “Next steps”- Architecture — how the pieces coordinate through storage.
- Queue configuration & retry policies — tune backoff, jitter, TTL.
- The outcome contract — success vs retryable vs permanent.
- Observability & metrics — DLQ depth and oldest-task-age alerts.