Examples
Background jobs
A team digest sent by background jobs on Inngest: a React page, a Hono API on Postgres, and a worker whose cron, fan-out, retries and onFailure send through Resend and bill through Stripe.
A team digest sent by background jobs on Inngest: a React page, a Hono API on Postgres, and a worker whose cron, fan-out, retries and onFailure send through Resend and bill through Stripe. See it on GitHub.
What it shows
- Background jobs on the real Inngest dev server:
settle()waits for every run, its fan-out, retries andonFailurehandler included time.travelto a digest's send time runs the hourly cron, which fans out onedigest/send-oneper subscriber in batches- Invariants that read Postgres against Resend and Stripe: one email per subscriber per digest, one invoice item per batch, and every failed copy told to its owner
danwo matrixfaulting the event sends, Resend, Stripe and the jobs' own SQL (58 trials), andJOBS_BUGswitches that turn off the idempotency keys or the steps
Stack
InngestHonoPostgresDrizzleViteReactEmulates: Resend, Stripe. Runs on: A Vite page, a Hono API and an Inngest worker in Chromium, with the Inngest dev server and Postgres in Docker.
Run it
Copy examples/jobs anywhere, then from its folder:
npm install
npx @danwo/cli testIts scripts:
npm run build # vite build web
npm run typecheck # tsc --noEmit && tsc --noEmit -p web
npm run test # danwo test
npm run test:agentic # danwo test tests/agentic.danwo.ts
npm run matrix # danwo matrix tests/deterministic.danwo.ts --grep "sends a scheduled digest"
npm run matrix:plan # danwo matrix tests/deterministic.danwo.ts --grep "sends a scheduled digest" --plan
npm run serve # danwo serveIts README says what else it needs and what the last full matrix found.
Until the danwo version it pins is on npm, install it from a local build instead: from the repository's root, bun run release <version> --pack, then node examples/link.ts jobs, which installs a copy and prints where it is.
Background jobs explains what this example leans on.
The test
import { resendInbox } from "@danwo/sdk"
import {
expectDigestsDelivered,
OWNER,
scheduleAndArrive,
subscriberRow,
TEAM,
test,
writeDigest,
} from "./digests.ts"
test("sends a scheduled digest to every subscriber once", async ({ app, screen, expect, time }) => {
await writeDigest({ app, screen, expect }, { title: "Friday notes", subscribers: TEAM })
await scheduleAndArrive({ screen, expect, time })
await expect(screen.getByTestId("digest-status")).toHaveText("Sent")
for (const person of TEAM) await expect(subscriberRow(screen, person.email)).toContainText("Sent")
await expect(screen.getByTestId("charge")).toHaveText(/Batch 1: 3 emails, \$0\.06/)
}).invariant(expectDigestsDelivered)
test("retries a send whose answer was lost, and still sends one email", async ({
app,
screen,
expect,
time,
proxy,
}) => {
/* Resend takes the first email, but the step never hears back, so Inngest runs it again. */
proxy.failNext((call) => call.host === "api.resend.com" && call.path === "/emails", {
kind: "reset-after",
})
await writeDigest({ app, screen, expect }, { title: "Release notes", subscribers: TEAM })
await scheduleAndArrive({ screen, expect, time })
await expect(screen.getByTestId("digest-status")).toHaveText("Sent")
await expect(screen.getByRole("row", { name: /Retried/ })).toContainText("Send one")
await expect(screen.getByRole("row", { name: /Sent to .* 2 Completed/ })).toBeVisible()
}).invariant(expectDigestsDelivered)
test("bills a batch once when Stripe's answer is lost", async ({
app,
screen,
expect,
time,
proxy,
}) => {
/* Stripe makes the invoice item, but the step never hears back, so Inngest runs it again. */
proxy.failNext((call) => call.host === "api.stripe.com" && call.path === "/v1/invoiceitems", {
kind: "reset-after",
})
await writeDigest({ app, screen, expect }, { title: "Usage report", subscribers: TEAM })
await scheduleAndArrive({ screen, expect, time })
await expect(screen.getByTestId("charge")).toHaveCount(1)
await expect(screen.getByTestId("charge")).toHaveText(/Batch 1: 3 emails, \$0\.06/)
}).invariant(expectDigestsDelivered)
test("a send that fails for good marks the copy failed and tells the owner", async ({
app,
screen,
expect,
time,
resend,
}) => {
/* The browser takes an address with no dot in its domain; Resend does not. */
const typo = { name: "Kim", email: "kim@northwind" }
await writeDigest(
{ app, screen, expect },
{ title: "Team update", subscribers: [...TEAM.slice(0, 1), typo] },
)
await scheduleAndArrive({ screen, expect, time })
await expect(screen.getByTestId("digest-status")).toHaveText("Sent with failures")
await expect(subscriberRow(screen, typo.email)).toContainText("Failed")
await expect(screen.getByRole("row", { name: /Send one: on failure/ })).toContainText(
`Told ${OWNER}`,
)
const [notice] = resendInbox(resend).list(OWNER)
expect(notice?.subject).toBe('Couldn\'t deliver "Team update" to kim@northwind')
expect(notice?.text).toContain("Invalid `to` field")
}).invariant(expectDigestsDelivered)
test("the digests page shows how each send went", async ({ app, screen, expect, time }) => {
await writeDigest(
{ app, screen, expect },
{ title: "Monday plan", subscribers: TEAM.slice(0, 2) },
)
await scheduleAndArrive({ screen, expect, time })
await writeDigest({ app, screen, expect }, { title: "Next week", subscribers: TEAM.slice(2) })
await screen.getByRole("button", { name: "Schedule" }).click()
await expect(screen.getByRole("status")).toContainText("Scheduled for")
await app.open("/")
await expect(screen.getByRole("row", { name: /Monday plan/ })).toContainText("Sent")
await expect(screen.getByRole("row", { name: /Next week/ })).toContainText("Scheduled")
}).invariant(expectDigestsDelivered)Commerce
A small shop selling mugs, notebooks and prints: a Next.js storefront and admin, a Hono API on Postgres, and Stripe Checkout, Resend receipts and S3 pictures.
SaaS with integrations
Relay, a team-notes app: workspaces on Clerk organizations, a Stripe trial and billing, Slack and Google Calendar connected by OAuth, live notes over Pusher, and Inngest jobs that send email through Resend.