synsema

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.

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§

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:

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, paste the first snippet, run synsema serve app.syn. The scheduler reference is in the docs; Lampson uses exactly this to run its scheduled tasks — see lampson.org.