Scheduled Tasks
Run code in your app on a schedule, such as nightly cleanups, data syncs and reminder emails, and manage the runs.
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 the job, and your agent writes it with the scheduling SDK. Stardeck then runs it on your schedule, retries failures, and records every run.
Ask your agent
Ask your coding agent in your app's checkout. It follows the SKILL.md inside
node_modules/@stardeck-customer-apps/scheduling-sdk. If you build in the browser, ask Starcat
Developer in the app's Build tab instead. 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 cron expression and writes the job. The schedule starts running once the change is on your live app.
Schedules run on their own on your live (production) app, and on a staging copy until it expires. In the sandbox they run only while someone has the app open in the dashboard, and a local dev server never runs them. To test a job before then, trigger it by hand. See Managing Schedules.
How It Works
Schedules are defined in your app's code with the scheduling SDK (@stardeck-customer-apps/scheduling-sdk, included in every app). Your agent writes this code. 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.
// 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.
// 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).
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).
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 tell the agent "9am Bangkok time" and it converts it.
| 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.
- Open your app in Stardeck
- Go to Settings and open Services → Schedules
From there you can:
- Pause and resume a schedule without removing it from your code
- Trigger a run manually, to test it 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
Your coding agent can do the same over MCP: list_app_schedules,
list_app_schedule_executions, set_app_schedule_status, trigger_app_schedule and
delete_app_schedule.
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.
Jobs that run only when there's work
A job that checks for work every few minutes ("any reminders to send?") queries your database on
every run, even when nothing is due. Ask your agent to make it adaptive instead. The cron becomes
the longest gap you accept, each run tells Stardeck when the next work is due, and the code that
creates work (a new booking, say) wakes the job early. The scheduling SDK's SKILL.md has the
pattern (adaptive: true, nextRun(), wakeSchedule()).
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.
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 run on their own only on your live app (and on a staging copy). 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.