---
title: Jobs
description: Durable task execution with typed inputs, progress, retries, and schedules.
pagination: false
sidebar:
  icon: list-todo
---

A job exposes work that can continue after the request that started it has ended. Define a typed task, expose it through a job, and let RELKIT persist the run, retry transient failures, and report progress.

> **Start here**
>
> Follow the [quickstart](/docs/modules/jobs/quickstart) to run an order export. For exact task and
> job options, open the [Jobs reference](/docs/api/jobs).

## Pre-setup

Use a native task provider in local Docker. Inngest is the starting path here;
[Trigger.dev](/docs/modules/jobs/providers/trigger) and [Effect MQ](/docs/modules/jobs/providers/effect-mq)
also have Docker recipes. Choose one and review its [capabilities](/docs/modules/jobs/providers/comparison).

1. **Install Inngest and local tooling**

    {/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

    From this guide's application/package root, install only its selected requirements:

    ```sh
    bun add @relkit/docker@0.7.12 @relkit/inngest@0.8.1 @relkit/local@0.7.12 inngest@4.20.0
    ```

    Keep these owner-specific versions and the lockfile together. Runtime SDKs belong in `dependencies`; build/type tooling belongs in `devDependencies`. Installing an adapter does not supply its optional SDK peers or enable the feature's configuration.

2. **Configure the jobs profile**

    Merge `jobs` and `defaults.jobs` into your root configuration. Keep your existing ID,
    `env` import and other settings; the example imports the environment contract from `src/platform/env.ts`.

    ```ts title="relkit.config.ts"
    /** Declares task application defaults with inspection disabled until selected. */
    import { defineApp } from "@relkit/app/config";
    import env from "@app/platform/env.js";
    import { docker } from "@relkit/docker";
    import { inngest } from "@relkit/inngest";

    export default defineApp({
      id: "relkit-inngest",
      env,
      server: { port: 3000, maxBodyBytes: 1_048_576 },
      telemetry: { redaction: { mode: "development-redacted", maxBytes: 65_536 } },
      jobs: { default: docker(inngest()) },
      defaults: { jobs: "default" },
    });
    ```

    Docker supplies the local provider's connection values; there are no new `defineEnv` secrets
    for this recipe. Jobs with `profile: "default"` use this binding. `localJob()` is the older
    function-target provider and is not a substitute for the native task provider used here.

3. **Check and start after registering the task and job**

    Check Docker before continuing. After adding the resource and its service registration, run from the app root:

    ```sh title="Terminal — application root"
    docker version
    bun run check
    bun run typecheck
    bun run dev
    ```

    `check` validates the graph; it starts no containers. `dev` starts only Docker recipes used by
    that graph, waits for readiness, and binds their generated connection values. An unused profile
    alone does not start a container. Keep the server running and use `bun run relkit local status`
    in another terminal to inspect the services. [Local Docker](/docs/modules/integrations/local-docker)
    covers detached startup and stopping services while retaining their volumes.

    Use `bun run relkit jobs capabilities` to inspect the selected provider, then follow
    [Your first durable job](/docs/modules/jobs/quickstart) to submit and observe a run.

## Start here

[Quickstart](/docs/modules/jobs/quickstart) — define a task and trigger its job.

[Tasks](/docs/modules/jobs/tasks) — the unit of durable execution.

[Bindings](/docs/modules/jobs/bindings) — choose a provider and bind jobs to it.

[Execution policy](/docs/modules/jobs/execution-policy) — timeouts, concurrency, and
cancellation.

[Retries and idempotency](/docs/modules/jobs/retries-and-idempotency) — safe retry behavior.

[Clients](/docs/modules/jobs/clients) — trigger, watch, and recover runs.

[Schedules](/docs/modules/jobs/schedules) — register provider-backed schedules.

[Providers](/docs/modules/jobs/providers) — compare native capability evidence.

## The model

```ts title="examples/commerce/src/services/orders/tasks/export-orders.task.ts"
/** Produces demonstration CSV through the ordinary order helper and emits validated progress around the durable export. */
import { defineTask } from "@relkit/app/tasks";
import { z } from "@relkit/app/schema";
import { exportOrderCsv } from "../helpers/fake-business.js";

const exportOrders = defineTask({
  id: "orders.export-orders",
  version: "1",
  execution: "durable",
  input: z.object({ orderIds: z.array(z.string()) }),
  output: z.object({ csv: z.string(), exported: z.number().int().nonnegative() }),
  progress: z.object({
    completed: z.number().int().nonnegative(),
    total: z.number().int().positive(),
  }),
  handler: async ({ orderIds }, context) => {
    await context.progress.emit({ completed: 0, total: orderIds.length });
    const csv = exportOrderCsv(orderIds);
    await context.progress.emit({ completed: orderIds.length, total: orderIds.length });
    return { csv, exported: orderIds.length };
  },
});

export default exportOrders;
```

```ts title="examples/commerce/src/services/orders/jobs/export-orders.job.ts"
/** Registers the export task as a public job exposing trigger, get, list and watch with progress/output fields. */
import { defineJob } from "@relkit/app/jobs";
import exportOrders from "../tasks/export-orders.task.js";

const exportOrdersJob = defineJob({
  name: "exportOrders",
  id: "orders.export-orders",
  task: exportOrders,
  client: {
    public: true,
    operations: ["trigger", "get", "list", "watch"],
    fields: ["status", "progress", "output"],
  },
});

export default exportOrdersJob;
```

Tasks own execution code, schemas, retry policy, limits, and hooks. Jobs own the public API name, admission, schedules, service selection, and client projection. The task ID, version, input schema, output schema, and policy are part of the durable identity of a run.

## Defaults worth knowing

- A run accepts at most 1 MiB of input and returns at most 1 MiB of output.
- A progress or stream item is limited to 64 KiB.
- The default retry policy is three attempts, one-second initial delay, 30-second maximum delay, factor two, and no jitter.
- Run lists default to 25 items and accept at most 100.

Read [provider comparison](/docs/modules/jobs/providers) before depending on durable sleep, native listing, or a schedule policy.

## Legacy API

The older function-target pages (`define`, `enqueue`, `first-job`, `retries`, `idempotency`, and `schedules`) remain available for migration. New code should use task-first jobs so task identity and compatibility checks are explicit.

{/* Generated by apps/docs/scripts/generate.ts. Do not edit. */}

**Reference and source**

- [`@relkit/jobs` API](/docs/api/jobs)
- [`@relkit/services` API](/docs/api/services)
- [`@relkit/client` API](/docs/api/client)

- [`examples/commerce/src/services/orders/jobs/export-orders.job.ts`](https://github.com/rel-kit/relkit/blob/main/examples/commerce/src/services/orders/jobs/export-orders.job.ts)
