--- title: "Cron" author: "@chris" author_url: https://elements.dev/u/chris published: 2026-10-02T15:11:56.357Z url: https://elements.dev/feed/01a0fd2c-0444-7b29-afe7-5b43a1ec5978 kind: lesson format: article --- # Cron by [@chris](https://elements.dev/u/chris) ยท 2026-10-02 ## Description Run a task on a recurring schedule, written in plain words, once across all your servers. Cron runs a task on a recurring schedule: every five minutes, every night at 2am, every Monday morning. It is for the work an app does on a clock rather than in response to a visitor: refreshing stats and metrics, sending the weekly newsletter, cleaning up old rows, expiring trials. Think of it as `setInterval` for your app, with a schedule written in plain words, and it runs once per tick however many servers you deploy. Cron comes with Elements, with no scheduler to set up. ## Define cron tasks in `index.ts` Cron tasks are defined in your app's `index.ts`, next to your routes, with `app.cron()`. Each one is a line: when it runs, a name for the logs, and the function to call. ```typescript index.ts app.cron("every 5m", "refresh stats", () => refreshStats()); app.cron("every day at 2am", "archive old rows", () => archiveOldRows()); app.cron("every monday at 9am", "weekly newsletter", () => new SendNewsletterJob().schedule()); ``` The function is ordinary server code, so it can call `sql()`, read config or call any of your helpers: ```typescript app/lib/archive.ts import { sql } from "@elements/app"; export function archiveOldRows() { sql(` update messages set archived = true where createdAt < now() - interval '90 days' `); } ``` ## Cron tasks run on the app server A cron task runs inside your app's process, the same process that serves your pages, the way a `setInterval` would. Every minute the app checks its schedules and runs each task that is due, one after another. So expensive work does not belong in a cron task. While it runs: - **It takes CPU from your app server.** Expensive work in a cron task uses the same process and CPU that serve your pages. - **Other cron tasks wait.** Tasks that are due run one after another, so a long one delays the rest. - **A failure is not retried.** A cron task has no queue, no retries and no timeout. If it throws or is cut off partway, that run is simply over until the next tick. Keep a cron task to quick work, like one query that updates a set of rows. Put expensive work in a job: the cron task schedules it, as the weekly newsletter above does, and returns at once. A separate worker process does the work, with retries, and the app server keeps serving pages. ## How several app servers coordinate Deploy your app to three servers and all three run the same cron schedules, but each tick runs on exactly one of them. Every minute, each server tries to claim that minute in a one-row table in your Postgres database. Postgres lets exactly one claim succeed; that server runs the tasks due that minute, and the others do nothing until the next one. So the 2am archive runs once a night, not three times, with no coordinator to run and no setting to change as you add servers. ## Every run is recorded Each run is saved in your database with its name, when it ran, how long it took and any error. Read the latest runs at any time: ```bash Terminal elements db -sql "select * from elements.cron_runs order by ran_at desc limit 20" ``` ## See cron in a demo - [Dealwren](https://elements.dev/demos/01a0f399-f3bb-7c53-af59-10b9fa312a32), a CRM that emails each rep their tasks every morning. The manual: [jobs](/learn/man/jobs), [database](/learn/man/database).