Durable background jobs on the Postgres you already have

Queues, retries, crons and durable step functions for TypeScript. Jobs live in their own schema of the database you already run — next to your data, not on someone else's servers.

A demo job, drawn by the same component as the console: attempt 1 times out, attempt 2 hits a rate limit, attempt 3 replays its first step instead of re-running it, and finishes. Select a bar to open its step below.

Background jobs today cost more than the work itself

Billed per step
Hosted orchestrators meter every step function invocation — the bill moves with how you wrote the code, not with what it does.
State far from your data
Job state and logs live on someone else's servers, often outside your region, so every query pays cross-region latency.
Redeploys and env sync
Hosted runners redeploy a copy of your code and expect every environment variable kept in sync by hand.

How it works

  • Your Postgres

    Peacock migrates a dedicated peacock schema into the database you already run — automatically, idempotently, on first connect. Nothing new to provision.

  • Two ways to run

    In-process workers today: point Peacock at DATABASE_URL and it runs alongside your app. Calling the serverless deploy you already have is planned.

  • Optional cloudPlanned

    An optional connection for alerts once retries are exhausted, and serverless wake-ups — never required to run Peacock.

Preview API
import { Peacock, serve } from "peacock";

// Reads DATABASE_URL, creates its schema on first run.
export const peacock = new Peacock();

const welcome = peacock.createFunction(
  { id: "send-welcome", event: "user/created", retries: 5 },
  async ({ event, step }) => {
    const { email } = event.data;
    await step.run("email", () => sendEmail(email));
    await step.sleep("wait", "3d");
    await step.run("follow-up", () => sendFollowUp(email));
  },
);

export const { GET, POST } = serve(peacock, [welcome]);
app/api/peacock/route.ts — the only file you add

Every attempt, every step

The job from the top of the page, step by step and line by line. Pick an attempt to follow it on its own.

Every task at a glance

Each registered task with its last 24 hours — runs, failures, p95 and the next cron tick — sorted worst first.

What's real today, and what's planned

Works today

  • Queues on Postgres — FOR UPDATE SKIP LOCKED claims, a worker pool, heartbeats, stale-job recovery
  • Retries with backoff, dead letter (including jobs with no handler), deadlines on stuck jobs
  • Durable step.run with memoization, step.sleep / step.sleepUntil, memoized step.sendEvent, replay traces
  • Cron with a distributed lock, so exactly one instance ticks
  • Concurrency limits per task, plus keyed concurrency per payload value
  • Payload validated against a zod schema before it runs
  • Per-job and per-attempt logs, persisted steps, task and queue statistics
  • The execution timeline above: every attempt, backoff waits, dead letters, and steps replayed from memoization

Planned

  • The plug-and-play SDK — serve(), zero-config init
  • Same code, two deploys — a push-HTTP route for your serverless function
  • Alerts that understand retries — one alert after retries are exhausted, with context
  • Serverless wake-ups from Peacock Cloud
  • A multi-app view across every Peacock-connected service

Run your first job on the Postgres you already trust

Early access is open. No credit card, no new infrastructure to provision.

Join early access