move flaky inline async work to durable, retryable background jobs with inngest. trigger on "background job", "queue", "retry failed work", "cron job", or "long-running task".
---
description: move flaky inline async work to durable, retryable jobs with inngest. trigger on "background job", "queue", "retry failed work", "cron job", "long-running task".
---
# durable background jobs
stop running fragile multi-step async work inline in a request. uses the verified inngest module for durable, retryable steps.
## intent
take a builder from "this api call sometimes fails and the user sees an error" to durable background work that retries each step independently. the failure mode this prevents: a single try/catch around three external calls, where one transient failure loses all the prior work.
## inputs
- an inngest account + signing key
- INNGEST_EVENT_KEY and INNGEST_SIGNING_KEY in env
- a serve route mounted for inngest to call back
## procedure
### step 1, define the function with discrete steps
each step is independently retried and its result memoized across replays.
```js
const { Inngest } = require('inngest');
const inngest = new Inngest({ id: 'app' });
const onSignup = inngest.createFunction(
{ id: 'on-signup', retries: 4 },
{ event: 'user/signup' },
async ({ event, step }) => {
await step.run('send-welcome', () => sendEmail(event.data.email));
await step.run('provision', () => provisionWorkspace(event.data.userId));
},
);
```
### step 2, send the event instead of doing the work inline
the request handler just emits, returns fast, and inngest runs the steps durably.
```js
await inngest.send({ name: 'user/signup', data: { userId, email } });
```
### step 3, tune retries + concurrency
set retries per function and concurrency limits to protect downstream apis.
## decision points
- step granularity: one external side effect per step, always.
- idempotency: steps can replay, make each side effect safe to repeat.
- concurrency: cap it for rate-limited downstreams.
## output contract
request handlers that emit events and return fast, durable functions with one side effect per step, and explicit retry + concurrency config.
## outcome signal
success means killing the process mid-job and watching it resume from the last completed step on replay, with no duplicated side effects. if a transient failure restarts the whole job, the steps are too coarse.
don't have the plugin yet? install it then click "run inline in claude" again.
expanded inputs with env var names and edge cases (missing keys, unreachable routes, rate limits, idempotency), added explicit decision branches for step granularity and idempotency safety, clarified output contract with data formats and error logging, added edge case guidance (inngest unreachable, step too coarse, replay duplicates).
stop running fragile multi-step async work inline in a request. uses the verified inngest module for durable, retryable steps.
take a builder from "this api call sometimes fails and the user sees an error" to durable background work that retries each step independently. the failure mode this prevents: a single try/catch around three external calls, where one transient failure loses all the prior work. use this when you have multi-step async workflows (email + provisioning, webhooks + database updates, etc.) that need to survive process crashes, network timeouts, and partial failures without losing intermediate progress.
edge cases to handle:
each step is independently retried and its result memoized across replays. one external side effect per step, always.
const { Inngest } = require('inngest');
const inngest = new Inngest({ id: 'app' });
const onSignup = inngest.createFunction(
{ id: 'on-signup', retries: 4 },
{ event: 'user/signup' },
async ({ event, step }) => {
await step.run('send-welcome', () => sendEmail(event.data.email));
await step.run('provision', () => provisionWorkspace(event.data.userId));
},
);
input: event name (e.g., 'user/signup'), event payload (userId, email), step definitions (send-welcome, provision). output: a durable function object registered with inngest.
the request handler just emits and returns fast. inngest runs the steps durably in the background.
app.post('/signup', async (req, res) => {
const { userId, email } = req.body;
await inngest.send({ name: 'user/signup', data: { userId, email } });
res.status(202).json({ status: 'processing' });
});
input: event name, event data (userId, email). output: event enqueued to inngest, request returns 202 accepted immediately.
inngest calls back into your app to run the function steps.
import { serve } from 'inngest/express';
const { onSignup } = require('./functions');
app.use('/api/inngest', serve({ client: inngest, functions: [onSignup] }));
input: inngest client, list of functions. output: POST /api/inngest route ready to receive inngest requests.
set retries per function and concurrency limits to protect downstream apis from rate limits and cascading failures.
const onSignup = inngest.createFunction(
{
id: 'on-signup',
retries: 4,
concurrency: {
key: 'event.data.email',
limit: 1, // serialize by email, prevent duplicate signups
},
},
{ event: 'user/signup' },
async ({ event, step }) => {
// ...
},
);
input: retry count, concurrency key and limit. output: function with backoff and rate limiting configured.
request handlers emit events and return fast (202 accepted). durable functions are defined with one side effect per step, retries configured per function, and concurrency limits set to protect downstreams. on success, each step is marked done and its result is cached. on failure, inngest retries the function (skipping completed steps) up to the retry limit, then logs a failure event you can hook into for alerting.
data format:
success means killing the process mid-job (e.g., kill -9 the node process while a step is running), waiting a few seconds, and watching inngest automatically resume the function from the last completed step on replay. if you see the send-welcome step re-executed and the same email sent twice, step memoization is not working (check inngest logs). if a transient failure restarts the whole function and duplicates all side effects, the steps are too coarse (merge them or make them idempotent). if inngest hangs waiting for a callback, the serve route is not reachable (check firewall, logs, and that the route is mounted).