> ## Documentation Index
> Fetch the complete documentation index at: https://developers.zite.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Scheduled jobs

> Run a workflow on a cadence — nightly digests, reminders, cache rebuilds — with an inline schedule, or let end users set their own.

Give a [workflow](/framework/workflows) a `schedule` field and it fires on that cadence — an *additional* trigger, so the workflow stays callable from your app too.

## Inline schedule

```typescript theme={null}
// apps/<app>/src/api/overdueReminders.ts
import { z } from 'zod';
import { createEndpoint } from 'zitejs/backend';
import { zite } from 'zitejs/db';
import { ZiteNotifications } from 'zitejs/notifications';

export default createEndpoint({
  description: 'Notify assignees of overdue tickets every weekday at 9am',
  schedule: {
    scheduleType: 'recurring',
    schedule: { frequency: 'weekly', interval: 1, daysOfWeek: ['mon', 'tue', 'wed', 'thu', 'fri'], times: ['09:00'] },
    timezone: 'America/New_York',
  },
  inputSchema: z.object({}),
  outputSchema: z.object({ notified: z.number() }),
  execute: async () => {
    // context.user is null on scheduled fires — don't depend on it here.
    const { rows } = await zite.sql({
      query: `
        SELECT "assigneeId", COUNT(*) AS n
        FROM "Tickets"
        WHERE "status" <> 'closed' AND "dueDate" < CURRENT_DATE
        GROUP BY "assigneeId"
      `,
    });
    for (const r of rows) {
      await ZiteNotifications.create({
        recipients: [String(r.assigneeId)],
        title: `You have ${r.n} overdue tickets`,
        link: { path: '/tickets' },
      });
    }
    return { notified: rows.length };
  },
});
```

The `schedule` is a structured object (recurring or one-time), not a cron string — see [Schedules](/framework/schedules) for every field. Always include an IANA `timezone`.

## Rules

* **`context.user` is `null` on scheduled fires** — don't depend on a signed-in user. (Also `null` on anonymous request fires, so it's not a reliable "this is a schedule" signal.)
* **Never simulate cadence in code.** The `schedule` field is the only source of truth — no `setInterval`/`setTimeout` or `if (week % 2)` skip-checks; serverless runs don't keep timers alive.

## Let end users set the schedule

For runtime, end-user-driven cadence (a "remind me every…" setting), use the `ZiteSchedules` runtime API from `zitejs/schedules` inside a workflow:

```typescript theme={null}
import { ZiteSchedules } from 'zitejs/schedules';

await ZiteSchedules.add({
  endpointId: 'sendReminder',          // typechecked against your real workflows
  schedule: { scheduleType: 'oneTime', fireAt: input.when, timezone: input.tz },
  inputs: { ticketId: input.ticketId }, // must match sendReminder's inputSchema
});
```

`inputs` are validated against the target workflow's `inputSchema` at fire time. See [Schedules](/framework/schedules) for `add` / `list` / `update` / `remove`.

<Note>
  Scheduled jobs are part of the Business plan.
</Note>
