"Run it every night" is one of the most common automation requests in any business application. Nightly reports to finance. Weekly digests to the management team. End-of-month rollups. Cleanup jobs that archive old records. Follow-up emails two weeks after a customer onboards. Shift reminders thirty minutes before they begin. Any workflow with every or after in its description is a scheduled workflow, and building a scheduler is one of those projects that sounds small but eats weeks when done from scratch. Having one built into the platform — tightly integrated with automations, isolated per tenant, and observable out of the box — means scheduled work is as easy to set up as any other piece of logic.
The scheduler offers two distinct scheduling models, covering the two ways scheduled work typically arises. Cron-style intervals handle the "every" pattern: every night at 2 a.m., every fifteen minutes, first of every month, every weekday at 9 a.m., and any other regular cadence. The schedule is attached to an automation in a simple configuration screen, and the automation runs exactly when it says. That's the common case, and for many tenants it's the only scheduling they'll ever need.
On-demand future runs handle the "after" pattern — the case where an automation triggered by an event wants to schedule a follow-up to happen some time later. An automation triggered by a new-customer signup might schedule a follow-up check-in email for two weeks out. A booking confirmation flow might schedule an SMS reminder to fire two hours before the appointment. An error-handling automation might schedule a retry in thirty minutes. In each case, the currently-running automation hands off to a future run with custom arguments carrying the context the future run will need. The scheduled task doesn't need to look anything up when it fires; it carries the data with it.
Cancel-task closes the loop on that pattern. An on-demand scheduled task can be cancelled by a later automation if circumstances change. The appointment got moved? Cancel the old reminder, schedule a new one for the new time. The order was cancelled entirely? Cancel the pending follow-up. That's what makes scheduled work feel reliable rather than fragile: the scheduler isn't a fire-and-forget queue; it's a queue with observable entries that can be inspected, modified, and removed.
Per-tenant isolation is the structural detail that matters at operational scale. Every tenant has its own schedule; failures in one tenant's scheduled work don't affect others; resource ceilings are applied per tenant. That means a misbehaving nightly job on one tenant doesn't degrade scheduled work across the platform. Underneath, the scheduler runs jobs on the real tenant environment rather than via any kind of symlinked or shared execution context, which prevents a category of surprise behaviors where a scheduled job on tenant A accidentally touches tenant B's state.
Auto-sync of cron configuration keeps the operational layer honest. When a tenant adds or removes a scheduled automation, the platform's cron configuration updates automatically — no manual operator intervention, no risk of a scheduled job drifting out of sync with the tenant's intent. Operators don't have to babysit the scheduler as tenants evolve their automations.
Memory management is a small but important consideration for scheduled jobs. Long-running automations — a nightly rollup that iterates over tens of thousands of records, a data-import cron that processes a large file — get memory limits appropriate to that work, rather than the tighter limits used for interactive API calls. The platform recognizes that scheduled work is a different kind of workload and sizes it accordingly, so the common "my nightly job runs out of memory at 3 a.m." problem simply doesn't happen.
Observability is handled by a dedicated scheduler log, which records every scheduled run with its timestamp, duration, outcome, and any errors. A toggle controls whether logging is on — some tenants prefer to keep it quiet, some prefer it always on — and the log itself is queryable and filterable the same way any other data in the tenant is. For admins debugging "did last night's job actually run?", the answer is one click away, with full context attached.
Worked example: a monthly invoice rollup. An implementer builds an automation that queries all invoices finalized in the previous month, groups them by customer, renders a PDF summary per customer, and emails each summary to the account owner. They attach a cron schedule — first day of every month at 6 a.m. — and that's the whole thing. On the first of each month the automation runs, each customer's account owner receives their rollup before 7 a.m., and the scheduler log records the outcome. If a particular month's run fails for a specific customer, the log shows which one; if the whole run fails, the admin sees it first because that's what the observability layer is for.
Worked example: a delayed reminder. A booking-confirmation automation that triggers when a customer books an appointment schedules a follow-up reminder for two hours before the appointment time. The follow-up is an on-demand scheduled task, carrying the appointment's identifier as its argument. When the reminder fires, the scheduled task looks up the current state of the appointment — not the state at booking time, but the current state — and sends the reminder only if the appointment is still active. If the appointment was moved or cancelled in the meantime, the original reminder is cancelled by whichever automation handled the move or cancellation. No stale reminders, no confused customers.
For tenants whose operations depend on scheduled work — which is most tenants, at some scale — the scheduler is the quiet feature that turns "we'd like to automate this" into "we did." Combined with automations, real-time delivery for live updates, and the notifications inbox, it's the operational heartbeat of the platform.