Skip to content

Scheduling

Jobs can be scheduled to start at a future time using the schedule option. The job is created transactionally but won’t be processed until the specified time.

// Schedule a job to run in 5 minutes
await withTransactionHooks(async (transactionHooks) =>
client.startJobChain({
transactionHooks,
typeName: "send-reminder",
input: { userId: "123" },
schedule: { afterMs: 5 * 60 * 1000 }, // 5 minutes from now
}),
);
// Or schedule at a specific time
await withTransactionHooks(async (transactionHooks) =>
client.startJobChain({
transactionHooks,
typeName: "send-reminder",
input: { userId: "123" },
schedule: { at: scheduledDate },
}),
);

The same schedule option works with continueWith for deferred continuations:

return complete(async ({ continueWith }) =>
continueWith({
typeName: "follow-up",
input: { userId: job.input.userId },
schedule: { afterMs: 24 * 60 * 60 * 1000 }, // 24 hours later
}),
);

For periodic tasks like daily digests, health checks, or billing cycles, start a new independent job chain from within the handler instead of using continueWith. This keeps each execution as its own short-lived chain rather than building an ever-growing chain history.

type Definitions = {
'daily-digest': {
entry: true;
input: { userId: string };
output: { sentAt: string };
};
};
// In processor — start a new chain with a scheduled delay
'daily-digest': {
attemptHandler: async ({ job, complete }) => {
await sendDigestEmail(job.input.userId);
return complete(async ({ sql, transactionHooks }) => {
if (userStillSubscribed) {
await client.startJobChain({
sql,
transactionHooks,
typeName: 'daily-digest',
input: { userId: job.input.userId },
schedule: { afterMs: 24 * 60 * 60 * 1000 }, // Run again tomorrow
});
}
return { sentAt: new Date().toISOString() };
});
},
}

See examples/showcase-scheduling for a complete working example demonstrating recurring jobs with scheduling and deduplication. See also Deduplication and Transaction Hooks.