# Cron Jobs

Schedule durable work without relying on an in-process timer or an external queue.

Ferrite schedules future work on durable Queue state. A restart does not erase
the due time, and a worker crash returns an unacknowledged lease for redelivery.

## Schedule delayed work

Use delayed queue publication for reminders, expirations, retry backoff, and
one-time future actions. Store a domain record and publish the schedule intent
with a stable idempotency key. When the job becomes visible, re-read the domain
record before applying the effect.

```rust filename="src/booking.rs"
let job = ReminderJob { booking_id, scheduled_for_ms };
reminders
    .publish_at(&serde_json::to_vec(&job)?, scheduled_for_ms)
    .await?;
```

The `fe-cal` application uses this pattern for expiring slot holds and sending
booking reminders. The booking state remains authoritative; a late or repeated
job cannot resurrect a cancelled booking.

## Recurring schedules

Represent a recurring schedule as durable state plus the next due occurrence.
After successfully applying one occurrence, atomically advance the next due
time and publish the next job. This avoids an always-on timer process and makes
missed or duplicated execution observable.

## Failure behavior

- Unacknowledged jobs are leased again after expiry.
- Attempt budgets move exhausted work to a dead-letter queue.
- Idempotency prevents a repeated lease from duplicating the domain effect.
- The worker checks current state so a stale reminder becomes a no-op.

See [Queues](/developers/queues/) for leases and dead letters, and
[Workflows](/developers/workflows/) for multi-step orchestration.
