Scheduled & delayed jobs
Two different needs: delay a single job ("retry this in 5 minutes") and recurring schedules ("every night at 02:00"). Forze handles the first on the queue itself, and the second through a durable backend's scheduler.
Delay a one-off job¶
enqueue takes a delay (relative timedelta) or a not_before (absolute
tz-aware datetime) — the message stays invisible until then:
from datetime import timedelta
queue = ctx.deps.resolve_configurable(ctx, QueueCommandDepKey, JOBS_QUEUE, route=JOBS_QUEUE.name)
await queue.enqueue("jobs", RetryCharge(invoice_id=id), delay=timedelta(minutes=5))
# or a fixed time:
await queue.enqueue("jobs", SendReminder(id=id), not_before=due_at) # tz-aware datetime
delay and not_before are mutually exclusive
Pass one or the other, never both. delay must be non-negative; not_before
must be timezone-aware. (enqueued_at is metadata only — it does not
delay delivery.)
Backend limits:
| Backend | Delayed delivery |
|---|---|
| SQS | up to SQS_MAX_DELAY (15 minutes) — longer delays need a scheduler |
| RabbitMQ | requires RabbitMQQueueConfig(delayed_delivery=True) on the route |
| Mock | honoured in-memory |
Recurring schedules (cron)¶
A queue delay is one-shot. For recurring work, register a durable function with a cron trigger — the durable backend (Temporal / Inngest) owns the schedule:
from forze.application.contracts.durable.function import (
DurableFunctionCronTrigger,
DurableFunctionSpec,
)
nightly = DurableFunctionSpec(
run=DurableFunctionInvokeSpec(...),
triggers=(DurableFunctionCronTrigger(expression="0 2 * * *"),), # 02:00 daily
operation="reports.nightly", # run the same frozen-registry operation as HTTP
)
Setting operation means the scheduled run executes the same handler your HTTP
routes do — one implementation, two triggers.
Notes¶
- The scheduler lives outside Forze — the queue/durable backend fires the trigger; Forze runs the work. There's no Celery Beat to operate.
- To schedule a job reliably as part of a transaction, stage it through the outbox rather than enqueuing inline.
- For a single deferred run with no recurrence, the queue
delayis simpler than a durable function.