# Cron jobs inside your server — schedules, webhooks and jobs without another service

> cron_every takes an interval or a five-field cron expression and runs a task on its own thread, next to your routes. No crontab, no worker fleet, no message broker — and a human gate when a job needs one.

Published 2026-09-02 · https://synsema.org/blog/cron-jobs-inside-your-server


A small product usually needs three processes before it needs three features: the API, a worker
for the jobs, and something to schedule them. In Synsema the scheduler is a builtin and the jobs
are tasks, so the three live in the file you already serve.

## A schedule is one line

```synsema
require serve(8080)
require net("api.warehouse.com")

task sync_inventory()
    let r be http_get("https://api.warehouse.com/stock")
    share json_decode(body of r) as "inventory"

task daily_report()
    log "report sent"

cron_every(300, sync_inventory)                                  -- every 5 minutes
cron_every("30 8 * * mon-fri", daily_report, {"tz": "-03:00"})   -- weekdays 08:30, fixed offset
cron_every("@hourly", sync_inventory)

serve on 8080
    route "GET /inventory"
        observe "inventory" as stock
        give stock
```

A number is an interval measured from the end of one run to the start of the next; a text is a cron
expression (`*`, ranges, `*/n`, `jan..dec`, `mon..fri`, `@daily` and friends), aligned to the minute.
Each job runs on its own thread, parked between ticks; runs never overlap; `cron_list()` and
`cron_cancel(name)` manage them at runtime.

## A job that needs a person

Some jobs should not finish on their own: the nightly cleanup that would delete a lot, the payment
run above a threshold. Under `serve`, `approve` queues the gate and the request waits for a human:

```synsema
task nightly_cleanup()
    let n be count_stale()
    when n > 1000 and not (approve "Delete " + text(n) + " stale rows?" within 30m)
        give nothing
    delete_stale()
```

The console prints the pending approval with a one-time token; with `SYNSEMA_HUMAN_WEBHOOK` set,
every gate also posts to your channel with ready-to-click yes/no links. Nobody answers in time — it
denies. An agent cannot fake the answer.

## What this replaces

A cron daemon, a queue, a worker process and the glue between them, for the common case of "run
this every N and let me know". When you do need a queue, the same file talks to Redis and Postgres
natively.

**Start here:** [install Synsema](/install), paste the first snippet, run `synsema serve app.syn`.
The scheduler reference is in the [docs](https://synsema.dev/en/0.6.x/34-cron); Lampson uses
exactly this to run its scheduled tasks — see [lampson.org](https://lampson.org).

