Skip to content

Scheduling

Wrap any preset in a JobSchedule and fire it on cron. Each fire runs CreateJobUsecase, which materializes a fresh Job through the preset’s server-side step resolution — so multi-step presets just work.

Via the SDK:

await client.jobs.createSchedule({
slug: 'daily-triage',
preset: 'gmailTriage',
cron: '0 7 * * *',
})

Or directly: POST /api/jobs/schedules. Names are namespaced server-side as {userId}.{slug}.

The policy is skip-if-previous-still-running. On each tick the worker checks IJobRepository.findInFlightBySchedule and skips the fire if the prior run hasn’t finished. This prevents two writers racing one export target.

POST /api/jobs/schedules/:name/run (SDK: runScheduleNow(slug)) is the only intentional bypass — don’t add others.

await client.jobs.listSchedules()
await client.jobs.getSchedule('daily-triage')
await client.jobs.runScheduleNow('daily-triage')
await client.jobs.deleteSchedule('daily-triage')

Internally, read-based presets keep a per-source ingestion watermark so each run advances from where the last one stopped instead of re-reading a fixed window. The watermark is a server-side mechanism with no SDK/API surface.