Skip to content

Schedules ​

Two kinds, with different routes and engines:

Job schedulesWorkflow schedules
EditionCorePro
Startsa job on a queuea run of a workflow
Declaredfrom SDK code (register_schedule), or YAML (schedulers:, sdk_schedules:)in the console or POST /api/v1/workflow-schedules (admin)
Managed/api/v1/schedules (pause, resume, run now)/api/v1/workflow-schedules (create, change, delete, pause, resume, trigger, backfill)
Fired bythe schedule runner, in the web process and in any --worker processthe engine's scheduler: a joobq-pro workflow Worker, or the web process with JOOBQUI_WORKFLOW_SCHEDULER=true

Job schedules from code (Core) ​

register_schedule (registerSchedule, RegisterSchedule, RegisterScheduleAsync) registers a cron or recurring job schedule with POST /api/v1/sdk/schedules, idempotent by name: registering the same name again updates it in place. Pass exactly one of cron (5 or 6 fields) or every ("<count> <unit>", for example "30 seconds", "5 minutes"; at least 1 second), plus the job type and the queue, and optionally timezone (default UTC), payload, max_retries, encrypt_fields and unique_key. The answer is {id, name, kind, created}.

python
@job("conf.register_sched", queue=QUEUE)
def register_sched_job(payload, ctx):
    probe = payload["probe"]
    res = ctx.register_schedule("conf-sched-" + LANG + "-" + probe, "conf.sched", QUEUE,
                                every="3 seconds", payload={"probe": probe})
    report(ctx, "register_sched", payload, id=res.get("id"))
ts
defineJob<App>({ name: 'conf.register_sched', queue: QUEUE, perform: async (p, ctx) => {
  const probe = p.probe
  const res = await ctx.registerSchedule(`conf-sched-${LANG}-${probe}`, 'conf.sched', QUEUE, { every: '3 seconds', payload: { probe } })
  return report(ctx, 'register_sched', p, { id: res.id })
} }),
ruby
class RegisterSchedJob
  include JoobQ::Job
  job_name "conf.register_sched"
  queue QUEUE
  def self.perform(p, ctx)
    probe = p["probe"]
    res = ctx.register_schedule("conf-sched-#{LANG}-#{probe}", "conf.sched", QUEUE,
                                every: "3 seconds", payload: { probe: probe })
    report(ctx, "register_sched", p, id: res["id"])
  end
end
go
joobq.DefineJob[*App]("conf.register_sched", opts, func(p map[string]any, ec *EC) (any, error) {
	probe := p["probe"]
	res, err := ec.Client.RegisterSchedule(ec.Ctx, joobq.ScheduleInput{
		Name:    fmt.Sprintf("conf-sched-%s-%v", lang, probe),
		JobType: "conf.sched",
		Queue:   queue,
		Every:   "3 seconds",
		Payload: map[string]any{"probe": probe},
	})
	if err != nil {
		return nil, err
	}
	return report(ec, "register_sched", p, map[string]any{"id": res.ID})
}),
rust
JobDef::new("conf.register_sched", queue.clone(), move |p: Value, ctx: &Ctx| {
    let probe = p["probe"].as_str().unwrap_or("").to_string();
    let client = JoobQClient::new(var("JOOBQ_GATEWAY_URL"), var("JOOBQ_SDK_TOKEN"));
    let res = client.register_schedule(ScheduleOptions {
        name: format!("conf-sched-{}-{}", sched_lang, probe),
        job_type: "conf.sched".into(),
        queue: sched_queue.clone(),
        every: Some("3 seconds".into()),
        payload: Some(json!({"probe": probe})),
        ..Default::default()
    })?;
    report(ctx, "register_sched", &p, json!({"id": res["id"]}))
}),
java
JobDefinition.<App>of("conf.register_sched", QUEUE, 3, (p, ctx) -> {
    String probe = (String) m(p).get("probe");
    Map<String, Object> res = ctx.registerSchedule("conf-sched-" + LANG + "-" + probe, "conf.sched", QUEUE,
        ScheduleOptions.every("3 seconds").payload(map("probe", probe)));
    return report(ctx, "register_sched", p, map("id", res.get("id")));
}),
php
final class RegisterSchedJob extends ConfJob
{
    public static function jobName(): string { return 'conf.register_sched'; }
    public static function perform(array $p, ExecutionContext $ctx): mixed
    {
        $probe = $p['probe'];
        $res = $ctx->client->registerSchedule('conf-sched-' . LANG . '-' . $probe, 'conf.sched', QUEUE, ['every' => '3 seconds', 'payload' => ['probe' => $probe]]);
        return report($ctx, 'register_sched', $p, ['id' => $res['id'] ?? null]);
    }
}
csharp
JobDefinition<App>.Define("conf.register_sched", Queue, 3, async (p, ctx) =>
{
    var probe = p?["probe"]?.GetValue<string>();
    var res = await ctx.RegisterScheduleAsync(name: "conf-sched-" + Lang + "-" + probe, queue: Queue, jobType: "conf.sched", every: "3 seconds", payload: new { probe });
    return await Report(ctx, "register_sched", p, new() { ["id"] = res?["id"]?.GetValue<string>() });
}),
Not in every language
  • Crystal: Gateway-mediated schedule registration: Crystal uses JoobQ's own YAML schedulers.

Tested against a real server in every language shown.

The schedule fires on the server and enqueues the job onto its queue on each tick; a worker that serves the queue runs it. Editing, pausing and deleting schedules are console operations, not part of the SDKs.

The console's Schedules page: each job schedule with its queue, next runs, last run, status, and Run now and Pause buttons.The console's Schedules page: each job schedule with its queue, next runs, last run, status, and Run now and Pause buttons.
The Schedules page: next runs, run now, pause and resume (mock data).

Job schedules in YAML (Core) ​

The server's YAML file (config/joobq.production.yml, or the file named by JOOBQ_CONFIG_FILE) can hold two sections:

yaml
sdk_schedules:            # a sibling of joobq:, for jobs an SDK worker runs
  - name: "nightly-report"
    every: "5 minutes"    # or: cron: "0 9 * * *"
    timezone: "UTC"
    queue: "reports"
    job_type: "GenerateReportJob"
    payload: { kind: "daily" }
    max_retries: 2

joobq: schedulers: holds cron_jobs and recurring_jobs of Crystal job classes compiled into the server. Each entry of sdk_schedules: needs name, queue, job_type and exactly one of cron or every; a bad entry is logged and skipped.

Who fires them. The web process and every --worker process run the schedule runner; whichever claims a tick in Redis first enqueues it, so several runners never double a job. Pausing skips ticks (they are not made up on resume).

Workflow schedules (Pro) ​

ProWorkflow schedules needs a JoobQ Pro key. Without one every Core feature keeps working: the Pro routes of the SDK gateway answer 402 pro_required and the console's Pro routes are not served. Get a licence · Core and Pro · Pro features index

A workflow schedule starts a run with the id sched-<schedule>-<fire time ms>, once per fire time whatever the number of processes. Exactly one of cron (five fields, evaluated in timezone) and interval_ms (1000 to 366 days); overlap decides what a due run does while the previous one is open (skip by default, buffer_one, allow_all, cancel_previous); catchup_window_ms (default 60000) runs fire times missed during downtime that are younger; max_runs, start_at and end_at bound it.

A schedule fires only while some process runs the engine's scheduler: a joobq-pro workflow Worker with its scheduler on, or the web process with JOOBQUI_WORKFLOW_SCHEDULER=true. Creating schedules without one is allowed, and they wait; GET /api/v1/workflow-scheduler says whether one is alive.

From SDK code. The SDK clients register job schedules only (their register_schedule sends kind: "job"). The gateway route itself accepts kind: "workflow" with workflow and input, and creates an engine workflow schedule whose runs reach the SDK workers (the engine sends their steps to the blueprint's task queue); without a Pro key that call answers 402 pro_required.

JoobQ Core is MIT licensed. JoobQ Pro needs a licence key.