v2.4.1
Getting started8 min read

Run your first durable workflow

A workflow is a sequence of steps that survives deploys, restarts and retries. In this guide you'll install the Cinder SDK, connect a project, and trigger a job that sends a receipt email.

MOMaya OkonkwoUpdated May 14, 2026Edit this page

Already have a project?

Skip to step 2 and reuse your existing API key. Everything else works the same way.

Overview

Each step in a workflow is checkpointed. If a deploy, timeout or rate limit interrupts a run, Cinder restarts at the first unfinished step instead of the beginning. The example below has three moving parts:

order.paidtrigger
load-orderstep
send-emailstep

A retry resumes at the last checkpoint, never at the start.

Before you begin

You'll need a recent Node.js runtime and a project to attach the workflow to. A sandbox project is enough for this guide.

Runtime
Node.js 20+
Package manager
npm, pnpm or bun
Account
Sandbox is fine

Create an API key in Project settings → Keys and keep it somewhere safe. You'll paste it into .env.local in a moment.

1

Install the SDK

Add the SDK to your project. It bundles the CLI, typed workflow helpers and the local runner, so one install covers the whole loop.

terminal
npm install @cinder/sdk

Using another package manager? Swap the command — the resulting lockfile is the only difference.

2

Connect a project

Create a config file at the repository root. The CLI and the runtime both read it, so local and deployed runs resolve the same project.

cinder.config.ts
import { defineConfig } from "@cinder/sdk";

export default defineConfig({
  project: "acme-receipts",
  region: "eu-west-1",
  retries: { attempts: 3, backoff: "exponential" },
});

Keep your key out of git

Store the key as CINDER_SECRET in .env.local. Cinder reads it at runtime and never ships it to the browser bundle.

Link the directory once. This writes a project id to .cinder/project.json; commit that file, not the key.

terminal
npx cinder link --project acme-receipts
3

Run a workflow

Create the job. It waits for order.paid, loads the order and sends the receipt. Each unit of work is a named step, which is what makes the run resumable.

src/jobs/send-receipt.ts
import { defineWorkflow } from "@cinder/sdk";

import { db } from "@/lib/db";
import { email } from "@/lib/email";

export const sendReceipt = defineWorkflow({
  id: "send-receipt",
  trigger: "order.paid",
  run: async ({ event, step }) => {
    const order = await step.run("load-order", () =>
      db.orders.find(event.data.orderId),
    );

    await step.run("send-email", () =>
      email.send({ to: order.email, template: "receipt", data: order }),
    );
  },
});

Start the local runner and trigger the sample event from the console. You should see the steps complete in order.

terminal
$ npx cinder link
Linked project acme-receipts (prj_4f21)

$ npx cinder dev
Cinder local runner ready on :4400
Watching src/jobs — 1 workflow registered

✓ send-receipt  queued      evt_8f2c1a  0.2s
✓ send-receipt  completed   run_91d7e0  2.8s

What just happened?

Cinder checkpointed load-order before calling the email provider. Kill the process mid-run, restart it, and the workflow resumes at send-email — no duplicate receipt.

Verify the setup

Three signals confirm the local loop is wired correctly. If any of them fails, run npx cinder doctor for a diff against the expected config.

CheckStatus
SDK resolves the configPassing
Workflow appears in the runnerPassing
Run completes with a receiptPassing

Next steps

You have a working workflow. These three guides cover the parts most teams add next.

Was this page helpful?

Sorry to hear that. Tell us what was missing in the community channel and we'll fix it.