Runnel 2.8: replay any workflow from a failed step.Read the release

Skip to content
Runnel

Runnel 2.8 · Workflow replays

Background jobs that finish, even when everything else fails

Write a job as a function in your own codebase. Runnel queues it, retries it, runs it on schedule and keeps a record of every attempt. TypeScript, Python and Go.

npm install @runnel/sdk

Free for 50,000 runs a month. No card needed.

jobs/send-invoice.ts
import { runnel } from '@runnel/sdk';

type Payload = { invoiceId: string };

export const sendInvoice = runnel.job({
  name: 'send-invoice',
  retries: { attempts: 5, backoff: 'exponential' },
  timeout: '2m',
  run: async ({ invoiceId }: Payload, ctx) => {
    const invoice = await billing.invoices.get(invoiceId);
    const html = renderInvoice(invoice);
    await mailer.send(invoice.customerEmail, html);
    ctx.log.info('invoice sent', { invoiceId });
  },
});

// anywhere in your app
await sendInvoice.enqueue({ invoiceId: 'inv_8812' });

Running background work for teams at

What Runnel does

The parts of a job queue you would otherwise build yourself

Retries, schedules, multi-step workflows and a record of every run, set next to the code they apply to.

  • Retries you set per job

    Attempts, backoff, timeouts and fatal errors, declared on the job. Retries are attempts of the same run, with one log, and they never count as extra runs.

    Retries and timeouts, Retries you set per job
  • Workflows that don’t repeat themselves

    Split a job into named steps. A finished step is stored, so when step four fails, the card charged in step two is not charged again.

    Workflows, Workflows that don’t repeat themselves
  • Schedules in your time zone

    Cron expressions with a time zone, defined in code and deployed with it. Pause one from the dashboard during an incident and resume it after.

    Schedules, Schedules in your time zone
  • Every run, searchable

    Payload, attempts, steps, logs and the error that ended it. Filter by job, status or step, and retry a whole batch of failures in one go.

  • Concurrency keys

    Limit how many runs a customer, a tenant or a rate-limited API can have at once, so one busy account never slows the rest down.

    Defining jobs, Concurrency keys
  • Your code stays with you

    Workers run in your own infrastructure and connect out over HTTPS. Nothing calls into your network, and your secrets never leave it.

    Deploying workers, Your code stays with you

How a retry works

A retry repeats the step that failed. Never the run.

Timeline of one fulfil-order run: reserve-stock runs once; charge-card fails with a 503, waits out an 8 second backoff, times out, waits 16 seconds and succeeds on its third attempt; create-invoice and send-email follow. The run succeeds in 45.4 seconds.
run_01JB7X2K9Q4MZ8 · fulfil-order · 3 attempts on charge-card · billed as one run

The dashboard

See what happened to every run, and fix it from the same screen

Each run shows its payload, every attempt with its logs and stack trace, and each workflow step with its timing. When a supplier has an outage, filter by the failing step, wait for it to recover and retry every affected run from that step.

  • Search runs by job, status, step or payload field
  • Retry or cancel one run or a filtered batch
  • Replay a workflow from the step that failed
  • Logs kept 3 to 90 days, depending on your plan
The Runnel overview: 71,240 runs this month at a 99.62% success rate, runs per day rising, the busiest workflows, and recent runs. One nightly-export run that failed at step 3 is ringed in blue, with a note: fix the bug, deploy, replay from step 3.

SDKs

The same model in TypeScript, Python and Go

Jobs, workflows and schedules work the same way in all three SDKs, and a job enqueued from one language can run on a worker written in another.

TypeScript and JavaScript

Node 18 or later, Bun and Deno. Payloads are typed from the job’s function, so enqueue checks them at compile time.

Install for TypeScript
workflows/onboard.ts
export const onboard = runnel.workflow({
  name: 'onboard-customer',
  run: async ({ userId }: { userId: string }, ctx) => {
    const account = await ctx.step('create-account', () => createAccount(userId));
    await ctx.sleep('wait-a-day', '1d');
    await ctx.step('send-tips', () => mailer.tips(account.email));
  },
});

Python 3.10 and later

Async-first, with plain functions run in a thread pool. Works with Django, FastAPI and any asyncio application.

Install for Python
workflows/onboard.py
@runnel.workflow(name="onboard-customer")
async def onboard(payload: dict, ctx) -> None:
    account = await ctx.step("create-account", lambda: create_account(payload["user_id"]))
    await ctx.sleep("wait-a-day", "1d")
    await ctx.step("send-tips", lambda: mailer.tips(account.email))

Go 1.22 and later

Generic jobs and steps with typed payloads and results, context cancellation and no reflection in the hot path.

Install for Go
workflows/onboard.go
onboard := runnel.NewWorkflow("onboard-customer",
	func(ctx runnel.Context, in Signup) error {
		acc, err := runnel.Step(ctx, "create-account", func() (Account, error) { return createAccount(in.UserID) })
		if err != nil {
			return err
		}
		if err := runnel.Sleep(ctx, "wait-a-day", 24*time.Hour); err != nil {
			return err
		}
		_, err = runnel.Step(ctx, "send-tips", func() (bool, error) { return mailer.Tips(acc.Email) })
		return err
	})
runs completed on Runnel Cloud in 2026 so far
2.1bn
API uptime over the last 12 months
99.99%
median time from enqueue to a worker starting
14 ms

Runnel Cloud, January to October 2026. Live figures on status.runnel.example.

From the teams using it

Fewer 3 a.m. pages, fewer cron boxes

  • “We had a Postgres table called job_attempts_v3 and a Slack channel for retrying things by hand. Both are gone. The dashboard answers the questions the channel used to.”

    Tomás HerreraPlatform lead, Quayside
  • “Our payout workflow touches four banks. Being able to say "this step ran once, here is the stored result" ended an argument with our auditors that had gone on for a year.”

    Hannah LindqvistEngineering manager, Ledgerline
  • “We are five engineers. Runnel means none of us is the person who understands the queue.”

    Kofi MensahCo-founder and CTO, Mosswood

Changelog

Shipped recently

A release every four to five weeks. Every change is in the changelog, with migration notes where you need them.

Full changelog

Run your first job in five minutes

Install the SDK, define a job and watch it run locally with runnel dev. Deploy when you’re ready. Free for 50,000 runs a month.