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

# Scheduled Tasks

> Run recurring jobs on a schedule—cleanups, syncs, digests, reports, and reminders

Stardeck can run code on a schedule. Use it for anything that should happen automatically on a recurring basis: nightly database cleanups, syncing data from another service, sending a weekly report, or emailing a daily digest.

You describe what you want in plain English and the agent wires it up. The platform then runs it on your schedule, retries failures, and records every run.

## Asking the Agent

The simplest way to add a schedule is to ask the agent. A few examples:

**Daily cleanup**

```
Every night at 2am, delete sessions that expired more than 7 days ago.
```

**Recurring digest email**

```
Every Monday at 9am, email each user a summary of their activity from the past week.
```

**Sync external data**

```
Every 15 minutes, pull the latest inventory from our supplier's API and update the products table.
```

**Scheduled reminder**

```
Each morning at 8am, send a reminder to customers who have an appointment that day.
```

The agent picks the right schedule, writes the job, and deploys it. Once your app is live, the schedule starts running.

<Info>
  Schedules run on your **live (production)** app. While you're still building in preview, you can
  trigger a schedule manually from the dashboard to test it—see [Managing
  Schedules](#managing-schedules).
</Info>

## How It Works

Under the hood, schedules are defined in your app's code using the scheduling SDK (`@stardeck-customer-apps/scheduling-sdk`, included in every app). You don't need to write this yourself—the agent does—but here's what it builds so you know what to expect.

### Defining a schedule

Each schedule has an `id`, a `cron` expression (in UTC), and a `handler`—the function that runs.

```typescript theme={null}
// lib/schedules.ts
import { defineSchedule } from "@stardeck-customer-apps/scheduling-sdk";

export const dailyCleanup = defineSchedule({
  id: "daily-cleanup",
  cron: "0 2 * * *", // Every day at 2:00am UTC
  description: "Delete expired sessions",
  handler: async (ctx) => {
    const deleted = await db.delete(sessions).where(lt(sessions.expiresAt, new Date()));
    return { deletedCount: deleted.count };
  },
});
```

### Wiring it up

Schedules are registered through a single catch-all route. The platform calls this route to discover your schedules and to run them.

```typescript theme={null}
// app/api/schedules/[[...path]]/route.ts
import { schedulingHandler } from "@stardeck-customer-apps/scheduling-sdk/nextjs";
import { dailyCleanup, weeklyReport } from "@/lib/schedules";

export const { GET, POST } = schedulingHandler({
  schedules: [dailyCleanup, weeklyReport],
});
```

When you deploy, the platform reads your schedule definitions and starts running them on the cron expressions you set. Requests between the platform and your app are signed, so only the platform can trigger your jobs.

### Schedule options

| Option        | Required | Default | Description                                            |
| ------------- | -------- | ------- | ------------------------------------------------------ |
| `id`          | Yes      | —       | Unique identifier for the schedule                     |
| `cron`        | Yes      | —       | Cron expression, evaluated in **UTC**                  |
| `handler`     | Yes      | —       | The function to run                                    |
| `description` | No       | —       | Human-readable label shown in the dashboard            |
| `maxRetries`  | No       | `3`     | How many times to retry if the handler throws          |
| `timeoutMs`   | No       | `30000` | Max run time before the execution is considered failed |

The handler receives a context object with `scheduleId`, `executionId`, `scheduledAt`, and `attemptNumber` (1 on the first try, higher on retries).

<Tip>
  If a handler does heavier work like sending a batch of emails, ask the agent to raise `timeoutMs`.
  The recommended ceiling is 5 minutes (`300000`).
</Tip>

## Cron Expressions

All cron expressions run in **UTC**, not your local timezone. If you want a job to run at 9am in your timezone, convert that time to UTC first (or just tell the agent "9am Bangkok time" and it will do the conversion).

| Expression    | When it runs             |
| ------------- | ------------------------ |
| `* * * * *`   | Every minute             |
| `*/5 * * * *` | Every 5 minutes          |
| `0 * * * *`   | Every hour, on the hour  |
| `0 */6 * * *` | Every 6 hours            |
| `0 0 * * *`   | Every day at midnight    |
| `0 2 * * *`   | Every day at 2:00am      |
| `0 9 * * 1-5` | Weekdays at 9:00am       |
| `0 9 * * 1`   | Every Monday at 9:00am   |
| `0 0 1 * *`   | First day of every month |

## Managing Schedules

Once your app is deployed, every schedule shows up in the dashboard.

1. Open your app in Stardeck
2. Click the **Cloud** button in the top-right
3. Go to the [**Schedules** tab](https://www.stardeck.ai/projects/~/dashboard/services/schedules)

From there you can:

* **Pause and resume** a schedule without removing it from your code
* **Trigger** a run manually—handy for testing before the next scheduled time
* **View execution history** for each schedule, with status, duration, and any error message
* **Delete** orphaned schedules that are no longer in your code

<Note>
  Schedules are discovered from your code on each deploy. If you rename a schedule's `id`, redeploy
  so the platform picks up the change. The old entry will appear as orphaned and can be deleted.
</Note>

## Retries and Failures

If a handler throws, the platform retries it automatically up to `maxRetries` times (default 3). Each retry increments `attemptNumber`, so your code can detect whether it's a first attempt or a retry.

```typescript theme={null}
export const syncInventory = defineSchedule({
  id: "sync-inventory",
  cron: "*/15 * * * *",
  maxRetries: 5,
  handler: async (ctx) => {
    if (ctx.attemptNumber > 1) {
      console.log(`Retry ${ctx.attemptNumber} for inventory sync`);
    }
    const items = await fetchInventory(); // throws → platform retries
    await upsertInventory(items);
    return { synced: items.length };
  },
});
```

Every attempt, success or failure, is recorded in the execution history so you can see exactly what happened.

## Common Questions

### My schedule isn't running

* Schedules only run on your **live** app, not in preview. Make sure your app is published.
* Confirm the deploy succeeded after you added the schedule.
* Check the execution history in the **Schedules** tab for errors. You can also trigger the schedule manually to test it.

### Why did it run at the wrong time?

Cron expressions are in **UTC**. A job set to `0 9 * * *` runs at 9:00am UTC, which is a different local time depending on your timezone. Ask the agent to set the schedule in your local time and it will convert it for you.

### Can I schedule a one-time task instead of a recurring one?

Schedules are built around recurring cron expressions. For a one-off action, set a schedule and have the handler check whether it has already run (for example, by reading a flag from your database), then pause or delete it afterward.

### My handler keeps timing out

The default timeout is 30 seconds. If your job does more work than that, ask the agent to raise `timeoutMs` (up to 5 minutes). For very large jobs, split the work across multiple runs.

***

Need help setting up a schedule? Reach out to [support@stardeck.ai](mailto:support@stardeck.ai).
