Cron Jobs and Scheduled Tasks
Run a daily report on a cron schedule inside a Rivet Actor, then add one-shot and fixed-interval jobs that survive sleep, restarts, and deploys.
Rivet Actor schedules are durable timers that live with the Actor. They survive sleep, restarts, upgrades, deploys, and crashes, so you do not need a separate cron service or a job queue to run work on time.
This guide adds a daily report to a reminders app. A reminderActor stores reminders that fire once, and a cron job on the same Actor sums them up every morning.
What we’ll build:
- A
reminderActorper key that stores reminders and the last daily report in its state. - A
daily-reportcron job, installed when the Actor is created, that runs every day at 9:00 Los Angeles time. - A
sendDailyReportaction that the job calls to build the report, save it, and broadcast it to connected clients. - One-shot reminders scheduled with
c.schedule.afterandc.schedule.at, and cancelled withc.schedule.cancel.
Install
npm add rivetkit @rivetkit/react
Create the reminder Actor
The Actor’s state holds the reminders and the bookkeeping the daily report needs:
import { actor, event, type ScheduledFireInfo, setup } from "rivetkit";
interface Reminder {
id: string;
scheduleId: string;
message: string;
scheduledAt: number;
completedAt?: number;
}
interface DailyReport {
from: number;
to: number;
completed: number;
pending: number;
}
interface ReminderActorState {
reminders: Reminder[];
completedCount: number;
lastReportAt: number;
lastReport?: DailyReport;
}
const reminderActor = actor({
state: {
reminders: [] as Reminder[],
completedCount: 0,
lastReportAt: 0,
} satisfies ReminderActorState as ReminderActorState,
events: {
reminderTriggered: event<Reminder>(),
dailyReport: event<DailyReport>(),
},
// Install the daily report job when the Actor is created.
onCreate: async (c) => {
await c.cron.set({
name: "daily-report",
expression: "0 9 * * *",
timezone: "America/Los_Angeles",
action: "sendDailyReport",
});
},
actions: {
// Called by the daily-report cron job every day at 9:00.
sendDailyReport: (c, fire: ScheduledFireInfo) => {
const from = c.state.lastReportAt;
const to = fire.scheduledAt;
// Skip a run whose window was already reported.
if (to <= from) return c.state.lastReport;
const report: DailyReport = {
from,
to,
completed: c.state.reminders.filter(
(r) =>
r.completedAt !== undefined &&
r.completedAt >= from &&
r.completedAt < to,
).length,
pending: c.state.reminders.filter(
(r) => r.completedAt === undefined,
).length,
};
c.state.lastReportAt = to;
c.state.lastReport = report;
c.broadcast("dailyReport", report);
return report;
},
// Return the last report, the next run time, and recent runs.
getDailyReport: async (c) => {
const job = await c.cron.get("daily-report");
const history = await c.cron.history("daily-report", { limit: 7 });
return {
lastReport: c.state.lastReport,
nextRunAt: job?.nextRunAt,
history,
};
},
// Schedule a reminder with a delay in milliseconds
scheduleReminder: async (c, message: string, delayMs: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.after(
delayMs,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: Date.now() + delayMs,
};
c.state.reminders.push(reminder);
return reminder;
},
// Schedule a reminder at a specific timestamp
scheduleReminderAt: async (c, message: string, timestamp: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.at(
timestamp,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: timestamp,
};
c.state.reminders.push(reminder);
return reminder;
},
// Trigger a scheduled reminder
triggerReminder: (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) {
console.warn(`reminder not found: ${reminderId}`);
return;
}
// Mark as completed
reminder.completedAt = Date.now();
c.state.completedCount++;
// Broadcast event
c.broadcast("reminderTriggered", reminder);
console.log(`reminder triggered: ${reminder.message}`);
},
// Get all reminders
getReminders: (c): Reminder[] => {
return c.state.reminders;
},
// Cancel a pending scheduled reminder.
cancelReminder: async (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) return { success: false };
const cancelled = await c.schedule.cancel(reminder.scheduleId);
if (cancelled) {
c.state.reminders = c.state.reminders.filter(
(r) => r.id !== reminderId,
);
}
return { success: cancelled };
},
// Get statistics about reminders
getStats: (c) => {
const total = c.state.reminders.length;
const completed = c.state.completedCount;
const pending = total - completed;
return {
total,
completed,
pending,
};
},
},
});
export const registry = setup({
use: { reminderActor },
});
export type Registry = typeof registry;
export type { DailyReport, Reminder };
registry.start();
remindersandcompletedCountbelong to the one-shot reminders you add in the last step.lastReportAtandlastReportbelong to the daily report. All of it is state, saved automatically.lastReportAtstarts at0, so the first report covers everything that happened before it.eventsdeclaresreminderTriggeredanddailyReport, the two typed events the Actor broadcasts.- Every schedule belongs to one Actor, so the key decides the scope of the job. The example uses a single key,
["main"], which gives one global job. Use one key per user or per resource for reminders, trials, billing periods, and other per-entity jobs.
Install the daily job
onCreate installs the cron job the first time the Actor is created:
import { actor, event, type ScheduledFireInfo, setup } from "rivetkit";
interface Reminder {
id: string;
scheduleId: string;
message: string;
scheduledAt: number;
completedAt?: number;
}
interface DailyReport {
from: number;
to: number;
completed: number;
pending: number;
}
interface ReminderActorState {
reminders: Reminder[];
completedCount: number;
lastReportAt: number;
lastReport?: DailyReport;
}
const reminderActor = actor({
state: {
reminders: [] as Reminder[],
completedCount: 0,
lastReportAt: 0,
} satisfies ReminderActorState as ReminderActorState,
events: {
reminderTriggered: event<Reminder>(),
dailyReport: event<DailyReport>(),
},
// Install the daily report job when the Actor is created.
onCreate: async (c) => {
await c.cron.set({
name: "daily-report",
expression: "0 9 * * *",
timezone: "America/Los_Angeles",
action: "sendDailyReport",
});
},
actions: {
// Called by the daily-report cron job every day at 9:00.
sendDailyReport: (c, fire: ScheduledFireInfo) => {
const from = c.state.lastReportAt;
const to = fire.scheduledAt;
// Skip a run whose window was already reported.
if (to <= from) return c.state.lastReport;
const report: DailyReport = {
from,
to,
completed: c.state.reminders.filter(
(r) =>
r.completedAt !== undefined &&
r.completedAt >= from &&
r.completedAt < to,
).length,
pending: c.state.reminders.filter(
(r) => r.completedAt === undefined,
).length,
};
c.state.lastReportAt = to;
c.state.lastReport = report;
c.broadcast("dailyReport", report);
return report;
},
// Return the last report, the next run time, and recent runs.
getDailyReport: async (c) => {
const job = await c.cron.get("daily-report");
const history = await c.cron.history("daily-report", { limit: 7 });
return {
lastReport: c.state.lastReport,
nextRunAt: job?.nextRunAt,
history,
};
},
// Schedule a reminder with a delay in milliseconds
scheduleReminder: async (c, message: string, delayMs: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.after(
delayMs,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: Date.now() + delayMs,
};
c.state.reminders.push(reminder);
return reminder;
},
// Schedule a reminder at a specific timestamp
scheduleReminderAt: async (c, message: string, timestamp: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.at(
timestamp,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: timestamp,
};
c.state.reminders.push(reminder);
return reminder;
},
// Trigger a scheduled reminder
triggerReminder: (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) {
console.warn(`reminder not found: ${reminderId}`);
return;
}
// Mark as completed
reminder.completedAt = Date.now();
c.state.completedCount++;
// Broadcast event
c.broadcast("reminderTriggered", reminder);
console.log(`reminder triggered: ${reminder.message}`);
},
// Get all reminders
getReminders: (c): Reminder[] => {
return c.state.reminders;
},
// Cancel a pending scheduled reminder.
cancelReminder: async (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) return { success: false };
const cancelled = await c.schedule.cancel(reminder.scheduleId);
if (cancelled) {
c.state.reminders = c.state.reminders.filter(
(r) => r.id !== reminderId,
);
}
return { success: cancelled };
},
// Get statistics about reminders
getStats: (c) => {
const total = c.state.reminders.length;
const completed = c.state.completedCount;
const pending = total - completed;
return {
total,
completed,
pending,
};
},
},
});
export const registry = setup({
use: { reminderActor },
});
export type Registry = typeof registry;
export type { DailyReport, Reminder };
registry.start();
c.cron.setstores a named recurring job on the Actor.0 9 * * *is a standard five-field cron expression for 9:00 every day.timezonetakes an IANA name and defaults to UTC. The job keeps firing at 9:00 local time across daylight saving changes.actionnames an action on the same Actor. The name is fixed in your code, never taken from a client.nameis unique per Actor. Callingcron.setagain with the same name updates the job instead of adding a second one, so there is no need to re-arm a one-shot schedule after every run.- The Actor does not stay awake between runs. It can sleep as usual, and Rivet wakes it when the job is due.
onCreate runs once per Actor, so a new expression in your code only reaches Actors created after the deploy. To update Actors that already exist, call the same cron.set from onWake, which runs on every start. Calling it with an unchanged expression keeps the next run time as it is.
Build the report
The cron job calls sendDailyReport, an ordinary action:
import { actor, event, type ScheduledFireInfo, setup } from "rivetkit";
interface Reminder {
id: string;
scheduleId: string;
message: string;
scheduledAt: number;
completedAt?: number;
}
interface DailyReport {
from: number;
to: number;
completed: number;
pending: number;
}
interface ReminderActorState {
reminders: Reminder[];
completedCount: number;
lastReportAt: number;
lastReport?: DailyReport;
}
const reminderActor = actor({
state: {
reminders: [] as Reminder[],
completedCount: 0,
lastReportAt: 0,
} satisfies ReminderActorState as ReminderActorState,
events: {
reminderTriggered: event<Reminder>(),
dailyReport: event<DailyReport>(),
},
// Install the daily report job when the Actor is created.
onCreate: async (c) => {
await c.cron.set({
name: "daily-report",
expression: "0 9 * * *",
timezone: "America/Los_Angeles",
action: "sendDailyReport",
});
},
actions: {
// Called by the daily-report cron job every day at 9:00.
sendDailyReport: (c, fire: ScheduledFireInfo) => {
const from = c.state.lastReportAt;
const to = fire.scheduledAt;
// Skip a run whose window was already reported.
if (to <= from) return c.state.lastReport;
const report: DailyReport = {
from,
to,
completed: c.state.reminders.filter(
(r) =>
r.completedAt !== undefined &&
r.completedAt >= from &&
r.completedAt < to,
).length,
pending: c.state.reminders.filter(
(r) => r.completedAt === undefined,
).length,
};
c.state.lastReportAt = to;
c.state.lastReport = report;
c.broadcast("dailyReport", report);
return report;
},
// Return the last report, the next run time, and recent runs.
getDailyReport: async (c) => {
const job = await c.cron.get("daily-report");
const history = await c.cron.history("daily-report", { limit: 7 });
return {
lastReport: c.state.lastReport,
nextRunAt: job?.nextRunAt,
history,
};
},
// Schedule a reminder with a delay in milliseconds
scheduleReminder: async (c, message: string, delayMs: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.after(
delayMs,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: Date.now() + delayMs,
};
c.state.reminders.push(reminder);
return reminder;
},
// Schedule a reminder at a specific timestamp
scheduleReminderAt: async (c, message: string, timestamp: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.at(
timestamp,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: timestamp,
};
c.state.reminders.push(reminder);
return reminder;
},
// Trigger a scheduled reminder
triggerReminder: (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) {
console.warn(`reminder not found: ${reminderId}`);
return;
}
// Mark as completed
reminder.completedAt = Date.now();
c.state.completedCount++;
// Broadcast event
c.broadcast("reminderTriggered", reminder);
console.log(`reminder triggered: ${reminder.message}`);
},
// Get all reminders
getReminders: (c): Reminder[] => {
return c.state.reminders;
},
// Cancel a pending scheduled reminder.
cancelReminder: async (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) return { success: false };
const cancelled = await c.schedule.cancel(reminder.scheduleId);
if (cancelled) {
c.state.reminders = c.state.reminders.filter(
(r) => r.id !== reminderId,
);
}
return { success: cancelled };
},
// Get statistics about reminders
getStats: (c) => {
const total = c.state.reminders.length;
const completed = c.state.completedCount;
const pending = total - completed;
return {
total,
completed,
pending,
};
},
},
});
export const registry = setup({
use: { reminderActor },
});
export type Registry = typeof registry;
export type { DailyReport, Reminder };
registry.start();
- When a schedule calls an action, it appends a
ScheduledFireInfoobject after the action’s own arguments. It carrieskind,id,name,scheduledAt, andfiredAt. - The report covers the window from
lastReportAttofire.scheduledAt. Using the planned time instead ofDate.now()keeps every window exactly one day long, even when a run starts a little late. - If the window was already reported, the action returns the saved report and changes nothing. Running it twice for the same occurrence is harmless, so a manual retry is safe.
- It saves the report to
c.state.lastReportand broadcastsdailyReport, so connected clients see it right away. - A run that is still going when the next one is due makes the next one skip. Failed runs are not retried; the job continues at its next normal time. See Execution behavior.
sendDailyReport is a public action, so any client that can reach the Actor can call it with any arguments. In production, check who connects with onBeforeConnect (see Authentication) and keep scheduled actions idempotent like this one.
Renaming or removing sendDailyReport deletes the daily-report job the next time it is due, because a recurring job is removed when its action no longer exists. Rename the action and the action field of cron.set in the same deploy, and call cron.set from onWake so existing Actors pick up the new name.
Check on the job
getDailyReport returns the last report, the job’s next run time, and its recent runs:
import { actor, event, type ScheduledFireInfo, setup } from "rivetkit";
interface Reminder {
id: string;
scheduleId: string;
message: string;
scheduledAt: number;
completedAt?: number;
}
interface DailyReport {
from: number;
to: number;
completed: number;
pending: number;
}
interface ReminderActorState {
reminders: Reminder[];
completedCount: number;
lastReportAt: number;
lastReport?: DailyReport;
}
const reminderActor = actor({
state: {
reminders: [] as Reminder[],
completedCount: 0,
lastReportAt: 0,
} satisfies ReminderActorState as ReminderActorState,
events: {
reminderTriggered: event<Reminder>(),
dailyReport: event<DailyReport>(),
},
// Install the daily report job when the Actor is created.
onCreate: async (c) => {
await c.cron.set({
name: "daily-report",
expression: "0 9 * * *",
timezone: "America/Los_Angeles",
action: "sendDailyReport",
});
},
actions: {
// Called by the daily-report cron job every day at 9:00.
sendDailyReport: (c, fire: ScheduledFireInfo) => {
const from = c.state.lastReportAt;
const to = fire.scheduledAt;
// Skip a run whose window was already reported.
if (to <= from) return c.state.lastReport;
const report: DailyReport = {
from,
to,
completed: c.state.reminders.filter(
(r) =>
r.completedAt !== undefined &&
r.completedAt >= from &&
r.completedAt < to,
).length,
pending: c.state.reminders.filter(
(r) => r.completedAt === undefined,
).length,
};
c.state.lastReportAt = to;
c.state.lastReport = report;
c.broadcast("dailyReport", report);
return report;
},
// Return the last report, the next run time, and recent runs.
getDailyReport: async (c) => {
const job = await c.cron.get("daily-report");
const history = await c.cron.history("daily-report", { limit: 7 });
return {
lastReport: c.state.lastReport,
nextRunAt: job?.nextRunAt,
history,
};
},
// Schedule a reminder with a delay in milliseconds
scheduleReminder: async (c, message: string, delayMs: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.after(
delayMs,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: Date.now() + delayMs,
};
c.state.reminders.push(reminder);
return reminder;
},
// Schedule a reminder at a specific timestamp
scheduleReminderAt: async (c, message: string, timestamp: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.at(
timestamp,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: timestamp,
};
c.state.reminders.push(reminder);
return reminder;
},
// Trigger a scheduled reminder
triggerReminder: (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) {
console.warn(`reminder not found: ${reminderId}`);
return;
}
// Mark as completed
reminder.completedAt = Date.now();
c.state.completedCount++;
// Broadcast event
c.broadcast("reminderTriggered", reminder);
console.log(`reminder triggered: ${reminder.message}`);
},
// Get all reminders
getReminders: (c): Reminder[] => {
return c.state.reminders;
},
// Cancel a pending scheduled reminder.
cancelReminder: async (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) return { success: false };
const cancelled = await c.schedule.cancel(reminder.scheduleId);
if (cancelled) {
c.state.reminders = c.state.reminders.filter(
(r) => r.id !== reminderId,
);
}
return { success: cancelled };
},
// Get statistics about reminders
getStats: (c) => {
const total = c.state.reminders.length;
const completed = c.state.completedCount;
const pending = total - completed;
return {
total,
completed,
pending,
};
},
},
});
export const registry = setup({
use: { reminderActor },
});
export type Registry = typeof registry;
export type { DailyReport, Reminder };
registry.start();
c.cron.getreturns the job’s settings andnextRunAt, the Unix time in milliseconds of the next run.c.cron.historyreturns recent runs, newest first. Each entry has aresultofrunning,ok,error, orskipped, plus the error when a run failed. Since failed runs are not retried, this is where you find them and build your own retry if you need one.- A job keeps 100 history entries by default. Pass
maxHistorytocron.setto keep up to 1,000, or0to turn history off.
Add one-shot jobs, intervals, and cancellation
The reminders are one-shot schedules. Each one calls triggerReminder once and keeps the schedule ID so it can be cancelled:
import { actor, event, type ScheduledFireInfo, setup } from "rivetkit";
interface Reminder {
id: string;
scheduleId: string;
message: string;
scheduledAt: number;
completedAt?: number;
}
interface DailyReport {
from: number;
to: number;
completed: number;
pending: number;
}
interface ReminderActorState {
reminders: Reminder[];
completedCount: number;
lastReportAt: number;
lastReport?: DailyReport;
}
const reminderActor = actor({
state: {
reminders: [] as Reminder[],
completedCount: 0,
lastReportAt: 0,
} satisfies ReminderActorState as ReminderActorState,
events: {
reminderTriggered: event<Reminder>(),
dailyReport: event<DailyReport>(),
},
// Install the daily report job when the Actor is created.
onCreate: async (c) => {
await c.cron.set({
name: "daily-report",
expression: "0 9 * * *",
timezone: "America/Los_Angeles",
action: "sendDailyReport",
});
},
actions: {
// Called by the daily-report cron job every day at 9:00.
sendDailyReport: (c, fire: ScheduledFireInfo) => {
const from = c.state.lastReportAt;
const to = fire.scheduledAt;
// Skip a run whose window was already reported.
if (to <= from) return c.state.lastReport;
const report: DailyReport = {
from,
to,
completed: c.state.reminders.filter(
(r) =>
r.completedAt !== undefined &&
r.completedAt >= from &&
r.completedAt < to,
).length,
pending: c.state.reminders.filter(
(r) => r.completedAt === undefined,
).length,
};
c.state.lastReportAt = to;
c.state.lastReport = report;
c.broadcast("dailyReport", report);
return report;
},
// Return the last report, the next run time, and recent runs.
getDailyReport: async (c) => {
const job = await c.cron.get("daily-report");
const history = await c.cron.history("daily-report", { limit: 7 });
return {
lastReport: c.state.lastReport,
nextRunAt: job?.nextRunAt,
history,
};
},
// Schedule a reminder with a delay in milliseconds
scheduleReminder: async (c, message: string, delayMs: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.after(
delayMs,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: Date.now() + delayMs,
};
c.state.reminders.push(reminder);
return reminder;
},
// Schedule a reminder at a specific timestamp
scheduleReminderAt: async (c, message: string, timestamp: number) => {
const id = `reminder-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
const scheduleId = await c.schedule.at(
timestamp,
"triggerReminder",
id,
);
const reminder: Reminder = {
id,
scheduleId,
message,
scheduledAt: timestamp,
};
c.state.reminders.push(reminder);
return reminder;
},
// Trigger a scheduled reminder
triggerReminder: (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) {
console.warn(`reminder not found: ${reminderId}`);
return;
}
// Mark as completed
reminder.completedAt = Date.now();
c.state.completedCount++;
// Broadcast event
c.broadcast("reminderTriggered", reminder);
console.log(`reminder triggered: ${reminder.message}`);
},
// Get all reminders
getReminders: (c): Reminder[] => {
return c.state.reminders;
},
// Cancel a pending scheduled reminder.
cancelReminder: async (c, reminderId: string) => {
const reminder = c.state.reminders.find((r) => r.id === reminderId);
if (!reminder) return { success: false };
const cancelled = await c.schedule.cancel(reminder.scheduleId);
if (cancelled) {
c.state.reminders = c.state.reminders.filter(
(r) => r.id !== reminderId,
);
}
return { success: cancelled };
},
// Get statistics about reminders
getStats: (c) => {
const total = c.state.reminders.length;
const completed = c.state.completedCount;
const pending = total - completed;
return {
total,
completed,
pending,
};
},
},
});
export const registry = setup({
use: { reminderActor },
});
export type Registry = typeof registry;
export type { DailyReport, Reminder };
registry.start();
scheduleReminderusesc.schedule.afterwith a delay in milliseconds.scheduleReminderAtusesc.schedule.atwith a Unix timestamp in milliseconds.- Both return a schedule ID, which the reminder stores as
scheduleId.cancelReminderpasses it toc.schedule.cancel, which returnsfalseif the reminder already fired. - Like the cron job, the target action name
triggerReminderis fixed in code, and the reminder ID is passed as its argument.
Pick the API by how often the work runs:
| API | Use it for |
|---|---|
c.schedule.after(delayMs, action, ...args) | Run once after a delay. Returns an ID for c.schedule.cancel. |
c.schedule.at(timestamp, action, ...args) | Run once at a Unix timestamp in milliseconds. Returns an ID for c.schedule.cancel. |
c.cron.set({ name, expression, action }) | Run on a calendar schedule, in a timezone. |
c.cron.every({ name, interval, action }) | Run every interval milliseconds, at least 5,000. Use it for frequent work such as presence sweeps or cache refreshes. Runs stay on their planned times, so a slow run does not push the next one later. |
c.cron.delete(name) | Stop a recurring job. Recurring jobs are managed by name, and calling cron.set or cron.every again with the same name replaces the job. |
See Schedule & Cron for c.schedule.list, c.cron.list, and the full set of options.
Try it
git clone https://github.com/rivet-dev/rivet.git
cd rivet/examples/scheduling
pnpm install
pnpm dev
Open http://localhost:5173. Schedule a reminder 5 seconds out and watch it move to completed when it fires. Schedule another and cancel it before it fires. The daily report runs at 9:00 Los Angeles time; getDailyReport shows its nextRunAt and, after the first run, the report and its history entry.
Next steps
- Schedule & Cron: every schedule and cron API, run history, and execution behavior.
- Lifecycle: when
onCreateandonWakerun, and how Actors sleep between scheduled runs. - Workflows: durable multi-step work for jobs that must finish even when a step fails.