Reliability

See how deliveries are retried, where to check a failed run, and what timing limits to expect.

. This page describes the delivery pipeline and its limits.

Delivery can happen more than once

Untimely delivers events at least once. Before any action runs, the schedule is claimed by inserting a run record guarded by a unique database index to prevent concurrent claims for the same schedule. If a run stops before completion, the claim expires after 5 minutes and the next retry re-executes the run instead of giving up.

A retry can send another copy of an email or repeat a webhook call. Make your webhook handler safe to call more than once. For event creation, use an Idempotency-Key header with the same request body when retrying. This prevents duplicate events; it does not deduplicate their later deliveries.

The pipeline, step by step

  1. A scheduler wakes every 5 minutes and processes due schedules within a 30-minute window. Each invocation reads at most 40 batches of 25 schedules to keep database work bounded. Remaining due schedules wait for a later invocation; schedules that age past the delivery window are recorded as missed.
  2. Each due schedule is handed to a message queue. The hand-off is recorded on the schedule, so a lagging queue does not cause the same schedule to be enqueued twice.
  3. A queue consumer claims the run, executes the event's actions in order (Email, Webhook), records every attempt, and marks the schedule triggered.
  4. Failed deliveries are retried or recorded as incidents, as described below.

Retry policy and dead-letter handling

A failed delivery message is retried up to 3 times with a 60-second delay between attempts, and each failure files an incident with the error and its context. A run whose worker died mid-execution becomes reclaimable after 5 minutes, so retries do real work rather than skipping an abandoned claim.

A message that exhausts every retry lands in a dead-letter queue. That queue has a consumer whose only job is to file a critical incident: a dead-lettered message is a user-visible dropped delivery, and it is treated as exactly that.

Missed schedules appear in history

If a schedule ages past its 30-minute delivery window without being attempted, it is marked as missed in your event history and files an incident for investigation.

Run history and retention

Every run writes two layers of records: the schedule run (claim time, start, completion, error) and per-action attempts (status, attempt number, input, output, error message, timing). Both are visible in the event's run history in the dashboard, where you can filter for failures. They are also available at GET /api/events/{id}/schedules/{scheduleId}. Completed schedule records are retained for 30 days and then deleted.

How failures are reported

Unhandled failures in API routes, the scheduler, and the queue consumer go through the same incident system:

  • The error is fingerprinted over its source, route, error type, message, and top stack frame, so a repeating failure collapses into one incident with an occurrence count instead of a flood.
  • Logs and state snapshots are captured with credentials and email addresses redacted before anything is stored.
  • A new fingerprint opens an incident record and automatically files a GitHub issue against our own codebase, including the running commit and a debugging brief. Repeat occurrences comment on the same issue with fresh context.
  • Near-identical failures that hash differently are deduplicated a second time at triage, so incidents do not fork.

When this pipeline detects a new failure, Untimely files its own bug report.

What you see when a delivery fails

Open an event's run history to check its scheduled runs and their status: succeeded, failed, or missed. A failed run shows which action failed, the attempt count, the duration, and the recorded error message. Failures caused by your own configuration (an unreachable webhook URL, a rejected recipient) are recorded in your run history the same way; use those errors to fix the destination. These configuration errors do not alert our team.

Status and limitations

  • Untimely runs on Cloudflare Workers, Queues, and D1, distributed across Cloudflare's network.
  • We do not publish a formal SLA today. If that changes, it will be documented here first.
  • At-least-once delivery means duplicates are possible during recovery from a crash.
  • Random schedules select a time inside your chosen window. The scheduler checks for due runs every 5 minutes, so delivery may happen after that time.
  • Check your event's run history first. If you still need help, contact support@untimely.app.

Create your first event →