Skip to content
cronpad
Concepts / How cronpad works

How cronpad works

cronpad is a single Go binary that serves the dashboard, the API, the MCP server, and also runs the scheduler. It keeps durable state in two SQLite files and a third, rebuildable SQLite file as the queue of what to run next. Nothing else needs to run. A single dispatcher goroutine pulls due work out of the queue and hands it to a pool of workers that deliver webhooks or run commands, recording every attempt.

The public website and documentation are a separate deployment at cronpad.com. The hosted dashboard runs at app.cronpad.com; self-hosted installations serve it at the root of their configured public URL.

Components

                ┌──────────────────────────── cronpad (one binary, one port) ─────────────────────────┐
 Browser ─────▶ │ /     dashboard                                                                      │
 API / CLI ───▶ │ /api/v1  REST API             /auth  Google, GitHub sign-in                         │
 AI agents ───▶ │ /mcp  MCP server                                                                    │
                │                                                                                     │
                │  claim loop ──claim──▶ queue: due, inflight (SQLite)  ──▶ workers ──▶ webhook/command│
                │     ▲                                                       │                       │
                │  reconciler, reaper                                  result writer, notifier ──▶ alerts
                └─────┬───────────────────────────────────────────────────────┬──────────────────────┘
                      │                                                       │
             cronpad.db (jobs, users, config)                     history.db (runs, attempts, audit)
                      └─────────── Litestream ──▶ S3 / GCS / Azure / SFTP ─────┘
ComponentWhat it does
HTTP serverDashboard, REST API, MCP server, OAuth sign-in and health checks. Works even while the scheduler is starting.
Claim loopOne goroutine. Atomically claims due occurrences from the queue and hands them to workers. Only runs while it holds the dispatcher lock.
WorkersA pool (64 by default) that executes sinks, classifies results, schedules retries and records attempts.
Result writerCommits database writes as soon as it is free, grouping whatever arrives meanwhile (up to 500 commands) into one transaction, so the hot path never waits on many small transactions.
ReaperReturns occurrences whose worker stopped renewing its lease (crash, hang) to the queue.
ReconcilerMakes sure the queue matches the databases: repairs lost entries, rebuilds a deleted or replaced queue file, handles missed runs after downtime.
NotifierEvaluates alert rules and sends email, Slack, Discord or webhook alerts from a durable outbox.
JanitorDeletes old history according to the retention policy and maintains daily rollups.

The life of one run

Take a job nightly-report scheduled every day at 9am in Asia/Kolkata.

  1. Create. The API validates the schedule, computes the next fire time (09:00 IST = 03:30 UTC), saves the job in cronpad.db with next_run_at, and adds an entry to the queue’s due table at 03:30 UTC.
  2. Claim. At 03:30:00 the claim loop wakes (it sleeps exactly until the earliest due time), and one transaction moves the entry from due to inflight with a 60-second lease.
  3. Schedule the next one. In durable mode, before delivering anything, the dispatcher computes tomorrow’s 09:00, writes it to cronpad.db and adds it to due. A slow webhook can never delay tomorrow’s run.
  4. Deliver. A worker creates the run record (fast mode can start while its commit is pending; see Delivery guarantees), sends the signed POST, and renews the lease while waiting.
  5. Record. The response is classified as success, retryable or fatal. History commits before job counters, and both commit before acknowledging the occurrence. The attempt is stored; on a retryable failure, a retry entry is added to due in the same atomic step that removes the in-flight entry.
  6. Measure. Start lag (03:30:00.006 − 03:30:00.000 = 6 ms), duration and end-to-end time go into metrics and the run record. If start lag exceeds the SLA threshold, the run is flagged and an alert can fire.

Source of truth and index

  • cronpad.db and history.db are the source of truth. Jobs, their next fire time, users and history live there. Litestream backs them up continuously.
  • The queue file is an index. It holds only “what runs when” and “what is running now”. It is never backed up: if it is lost, deleted or corrupt, cronpad rebuilds it from cronpad.db automatically. See Scheduling engine.

Read Storage for why SQLite was chosen and its limits.

One binary, one entry point

There is no separate frontend to deploy. API-only mode is a setting ([ui] dashboard = false), not a different build. See Editions for how the open-source and cloud editions differ.