Scheduling
Deferred Start
Section titled “Deferred Start”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.
await withTransactionHooks(async (transactionHooks) => client.startJobChain({ transactionHooks, typeName: "send-reminder", input: { userId: "123" }, schedule: { afterMs: 5 * 60 * 1000 }, // 5 minutes from now }),);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 }),);Triggering Scheduled Jobs Early
Section titled “Triggering Scheduled Jobs Early”A scheduled job is just a pending job with a future scheduledAt. Use triggerJob (or triggerJobs for a batch) to override the schedule and run it immediately — useful for admin “run now” actions or manually advancing a queued reminder.
await withTransactionHooks(async (transactionHooks) => client.triggerJob({ transactionHooks, id: jobId, }),);triggerJob throws JobNotFoundError if the job does not exist and JobNotTriggerableError if it is not pending (e.g. already running, completed, or blocked).
triggerJobs validates atomically — if any id in the batch is missing or not pending, the entire call fails and no job is triggered. Both methods are mutating and require transactionHooks + a transaction context.
Recurring Jobs
Section titled “Recurring Jobs”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.
const jobTypeRegistry = defineJobTypeRegistry<{ '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 deduplication: { key: `digest:${job.input.userId}`, excludeJobChainIds: [job.chainId], // Skip the completing chain }, }); } 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.