Replay any workflow from a failed step
- New
- Improved
2.8.0
Runnel 2.8: replay any workflow from a failed step.Read the release
Runnel 2.8 · Workflow replays
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.
Free for 50,000 runs a month. No card needed.
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' });What Runnel does
Retries, schedules, multi-step workflows and a record of every run, set next to the code they apply to.
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 jobSplit 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 themselvesCron 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 zonePayload, 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.
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 keysWorkers 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 youHow a retry works

The dashboard
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.

SDKs
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.
Node 18 or later, Bun and Deno. Payloads are typed from the job’s function, so enqueue checks them at compile time.
Install for TypeScriptexport 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));
},
});Async-first, with plain functions run in a thread pool. Works with Django, FastAPI and any asyncio application.
Install for Python@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))Generic jobs and steps with typed payloads and results, context cancellation and no reflection in the hot path.
Install for Goonboard := 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
})Runnel Cloud, January to October 2026. Live figures on status.runnel.example.
From the teams using it
“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, MosswoodChangelog
A release every four to five weeks. Every change is in the changelog, with migration notes where you need them.
2.8.0
2.7.0
2.6.0
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.