Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Cron trigger

Fire a workflow on a schedule. No chain config needed — a cron → HTTP → notify project runs with zero networks and zero signers.

workflows:
  daily-report:
    trigger:
      cron:
        expression: "0 9 * * *"    # every day at 09:00 UTC
        catch_up: true             # optional — replay ticks missed while down
    steps:
      - id: fetch
        http_call:
          url: https://api.example.com/stats
      - id: report
        notify:
          channel: ops
          message: "daily stats: ${{ steps.fetch.output.body }}"
FieldRequiredDescription
expressionStandard 5-field cron expression (minute hour day month weekday)
tzUTCIANA timezone name, e.g. Europe/London. Ticks compute in the named zone (DST-correct); an unknown name is a startup error. UTC / Etc/UTC mean the plain UTC path. See Timezones
catch_upfalseReplay schedule slots missed while rflow was down — see below

The trigger payload exposes trigger.scheduled_for (the ISO tick) and trigger.workflow to expressions.

Exactly-once for schedules

Each scheduled firing claims a trigger key derived from its scheduled time (never now()), exactly like events claim chain_id:tx_hash:log_index — so a restart around a tick never double-fires.

catch_up — missed ticks

By default, a tick that comes due while the process is down is skipped: a restart schedules from the next future occurrence.

With catch_up: true, the last fired tick is persisted per workflow (rflow.cron_state) and on boot every slot missed while rflow was down is claimed — each with its own scheduled-time key, so a repeated boot can never double-fire the same tick.

Timezones

By default a cron runs in UTC. Set tz to any IANA timezone name and the schedule is computed on that zone's local wall clock:

cron:
  expression: "0 9 * * 1"    # every Monday at 09:00 LOCAL
  tz: "Europe/London"        # 09:00 GMT in winter, 09:00 BST in summer

The zone is validated against the IANA database at config-validation time — an unknown name (rflow validate or boot) is a hard error, never silently UTC.

DST is handled honestly:

  • Spring forward (a skipped hour): a tick that lands in the gap does not hang and does not double-fire — it snaps to the first valid instant after the gap.
  • Fall back (a repeated hour): a fixed-time tick fires once, not twice.

The dedupe key stays a UTC instant regardless of zone, so exactly-once holds across restarts and daylight transitions exactly as it does for UTC crons.

Examples

cron: { expression: "*/5 * * * *" }                        # every 5 minutes
cron: { expression: "0 * * * *" }                          # hourly, on the hour
cron: { expression: "0 9 * * 1", catch_up: true }          # Mondays 09:00 UTC, replayed if missed
cron: { expression: "0 9 * * 1", tz: "America/New_York" }  # Mondays 09:00 New York time

For a fixed cadence (every: 30s) rather than a calendar schedule, use the interval trigger — it allows sub-minute cadences, which cron cannot express.

Combine with send_transaction for DCA and treasury sweeps, or keep it fully off-chain. To rehearse a cron workflow's steps without waiting for the schedule, use rflow test.