Skip to content
cronpad
Concepts / Delivery guarantees

Delivery guarantees

cronpad guarantees at-least-once delivery: every scheduled run is attempted, and in rare failure cases it may be delivered twice. It never silently drops a run while its database is intact. Every run records how late it started. The Cloud Pro on-time objective is 99.9% within 5 seconds per calendar month, with the exclusions defined below. Each job can also set its own SLA threshold (5 seconds by default) to flag and alert on individual late runs.

At least once

A run is delivered more than once only in these situations:

SituationWhy it duplicates
cronpad stops (crash, kill -9, power loss) after your endpoint received the request but before cronpad recorded the resultcronpad can’t know the request arrived, so the next process delivers it again.
The queue file can’t be written for longer than the lease (60 s) while a request is in flight (for example, a full disk)The lease can’t be renewed; the work is considered abandoned and is redelivered.
A retry after a timeoutYour endpoint may have done the work but responded too slowly. The retry is a new attempt of the same run.

Making your endpoint idempotent

Every request for a run carries the same identifier on every attempt and every redelivery:

HeaderValueStable across
Idempotency-KeyRun ID, e.g. run_01J9ZM1A…Retries and redeliveries
webhook-idSame run ID (Standard Webhooks)Retries and redeliveries
Cronpad-Attempt0, 1, 2…Changes per attempt

Store the run ID when you finish the work, and skip work you’ve already done for that ID. If your endpoint already supports Idempotency-Key (many payment and email APIs do), point cronpad at it directly.

Durable and fast delivery

scheduler.delivery_mode = "durable" is the default in both self-hosted and hosted mode. The run is recorded before the delivery starts, and the result is recorded before cronpad acknowledges it.

Opt-in "fast" starts the first attempt as soon as the run is queued for recording, so delivery overlaps the database commits. If cronpad crashes in that short window, the receiver may already have acted on a request that was never recorded; after recovery the run is delivered again with the same Idempotency-Key and webhook-id. If the job was deleted in between, recovery records the run as cancelled with the reason job deleted before recovery; delivery may have occurred and does not redeliver. In-flight work survives the crash in the queue file, so recovery finds it on the next start.

Receivers must deduplicate with Idempotency-Key or webhook-id before you enable fast mode. In fast mode the recorded start time of a replayed run is the replay’s, since the original delivery’s timing and response are unknown.

Runs that are subject to a monthly quota, a concurrency cap or skip_if_running always wait for admission, even in fast mode, so limits and overlap policy hold. A slow endpoint never blocks the schedule. Shutdown drains in-flight runs and flushes pending records in both modes.

Never silently dropped

  • Each active job’s next fire time is stored in cronpad.db before cronpad relies on the queue for it.
  • If the queue entry for a job disappears for any reason, the reconciler notices that the job is overdue and re-queues it. If the whole queue file is deleted, emptied or replaced, cronpad detects the missing epoch and rebuilds the queue. Pending and in-progress runs are stored durably before the schedule advances, so a rebuild also restores work whose lease disappeared; a run invalidated by a job edit is not revived.
  • Runs missed while cronpad was down are handled by the job’s misfire policy: fire once now (default), fire each, or skip. Skipped runs are still recorded with status skipped, so the gap is visible.

On time

Each run records start_lag_ms: the time between scheduled_at and the start of its first attempt.

  • A run breaches its SLA when start_lag_ms is greater than the job’s SLA threshold (job setting sla_threshold, or the server default of 5s).
  • Breaches are recorded on the run (sla_breached: true), counted in the cronpad.runs.sla_breached metric, and can trigger a run.sla_breached alert.
  • Retries don’t count against start lag; they’re measured by e2e_ms.

Start lag includes timer slack and the durable database commits that record the start (a single job typically starts 2–4 ms after its scheduled time). Lag grows when more work is due at once than there are workers (for example thousands of jobs at exactly midnight); cronpad still runs everything, and the metrics show the lag honestly rather than hiding it.

Cloud SLO and SLA

A service-level objective (SLO) is a published performance target, without service credits. A service-level agreement (SLA) is a contractual commitment with defined service credits. Contact us for availability and terms.

  • Cloud Free: best effort.
  • Cloud Pro: at least 99.9% of scheduled runs per UTC calendar month start on time, excluding announced maintenance windows and runs delayed by the workspace’s own concurrency cap. There are no service credits.
  • Cloud Enterprise: a contractual on-time SLA with service credits on dedicated single-tenant instances. The exact target and credit terms are agreed with the customer.
  • No credit-bearing SLA is offered on shared infrastructure.

On time means start_lag ≤ 5s for the first attempt. The start is the outbound request being sent or the command starting; endpoint response time is excluded. Retries are measured separately with e2e_ms. A job’s sla_threshold remains its individual alert threshold, not a credit-bearing contract. Self-hosted timing depends on your hardware and configuration; benchmark figures are not Cloud commitments.

Maintenance

Announced maintenance windows are excluded from the monthly SLO calculation. Affected runs still fire late and are never silently lost. Deploys can pause scheduling for a few seconds.

Visible admission exceptions

Licensing, plan downgrades and billing problems never stop the scheduler. An exceeded Free-plan quota (or an optional Pro spend cap) records new occurrences as skipped with quota_exceeded; an operator suspension for abuse records skipped runs with workspace_suspended. These apply only to admission; runs that were admitted are delivered normally. Retries never consume monthly quota.

Ordering

  • No ordering is guaranteed between different jobs that are due at the same moment.
  • For one job, the next occurrence fires on schedule even if the previous run is still retrying, unless you set overlap_policy to skip_if_running. See Misfires and overlap.

What cronpad does not guarantee

  • Exactly-once. No scheduler can guarantee it over HTTP; use the idempotency key.
  • Delivery to an endpoint that is down for longer than your retry policy allows. After the last attempt the run is failed, you get an alert if you configured one, and after enough consecutive failures the job is auto-disabled.
  • Uptime during a self-hosted deploy. A restart pauses scheduling for a few seconds; runs due in that window start late (and are marked as SLA breaches) but are not lost.