Skip to main content

Actions & Scheduling

Everything in CallMeLater is an action -- something scheduled to happen in the future. Actions operate in one of two modes: webhook or approval.

Webhook mode

Webhook mode is the default. A webhook action delivers an HTTP request to your endpoint at a scheduled time.

{
"schedule": { "preset": "tomorrow" },
"request": {
"method": "POST",
"url": "https://api.example.com/webhook",
"headers": { "X-Custom-Header": "value" },
"body": { "event": "trial_expired", "user_id": 42 }
}
}

Use webhook mode for trial expirations, delayed notifications, scheduled API calls, cleanup tasks, and follow-up triggers.

Approval mode

An approval action sends an interactive message to one or more recipients and collects their response (confirm, decline, or snooze).

{
"mode": "approval",
"schedule": { "wait": "2h" },
"gate": {
"message": "Please confirm the deployment",
"recipients": ["ops@example.com", "+1234567890"],
"confirmation_mode": "first_response"
},
"callback_url": "https://api.example.com/webhooks/response"
}
info

Approvals do not automatically execute anything. When someone responds, CallMeLater sends the response to your callback_url. Your system decides what to do next.

Use approval mode for approval workflows, human confirmations, check-ins, and escalation chains.

Scheduling

Every action needs a schedule that defines when it should fire. There are three ways to set it.

Presets

Named time references, resolved relative to now (or to a timezone if provided):

PresetWhen
tomorrowTomorrow at the current time
next_mondayNext Monday at the current time
next_tuesdayNext Tuesday at the current time
next_wednesdayNext Wednesday at the current time
next_thursdayNext Thursday at the current time
next_fridayNext Friday at the current time
next_saturdayNext Saturday at the current time
next_sundayNext Sunday at the current time
next_weekNext Monday at the current time
1h, 2h, 4h1, 2, or 4 hours from now
1d, 3d1 or 3 days from now
1w1 week from now
{
"schedule": {
"preset": "next_monday",
"timezone": "America/New_York"
}
}

Relative wait

A duration from now using schedule.wait:

{ "schedule": { "wait": "30m" } }
FormatMeaningExample
NsN seconds30s = 30 seconds
NmN minutes5m = 5 minutes
NhN hours2h = 2 hours
NdN days1d = 1 day
NwN weeks1w = 1 week
NMN months1M = 1 month

Exact time

An ISO 8601 UTC timestamp using scheduled_for:

{
"scheduled_for": "2026-04-01T14:30:00Z"
}

Timezone

Optional. Defaults to UTC. Provide a timezone when using presets so that times like tomorrow and next_monday resolve to the correct local time.

{
"schedule": {
"preset": "tomorrow",
"timezone": "Europe/Paris"
}
}

Lifecycle states

Actions move through a series of states from creation to completion.

Webhook mode:

scheduled → resolved → executing → executed
↘ failed

Approval mode:

scheduled → resolved → executing → awaiting_response → executed
↘ failed
↘ expired

Any non-terminal action can also be cancelled.

StateDescriptionNext states
scheduledSchedule is being resolved to an exact timestampresolved, cancelled
resolvedWaiting for the scheduled time to arriveexecuting, cancelled
executingHTTP request or reminder is being deliveredexecuted, failed, awaiting_response, resolved (retry)
awaiting_responseReminder sent, waiting for a human replyexecuted, failed, expired, cancelled, scheduled (snooze)
executedCompleted successfully (terminal)--
failedFailed permanently (terminal)resolved (manual retry)
expiredApproval timed out without response (terminal)--
cancelledCancelled before completion (terminal)--

Recurring actions

Any action can repeat on a schedule by adding a recurrence configuration. After each successful execution, the action automatically re-schedules itself for the next occurrence.

{
"schedule": { "wait": "5m" },
"request": { "url": "https://api.example.com/health-check" },
"recurrence": {
"frequency": 1,
"unit": "h",
"end_type": "count",
"max_occurrences": 24
}
}

How it works

  1. The action executes normally at the scheduled time
  2. On success, it re-schedules itself by the configured interval
  3. The recurrence_count increments after each execution
  4. When the end condition is met, the action reaches executed (terminal)
  5. Cancelling the action at any point stops all future occurrences

Lifecycle

A recurring action cycles through resolved → executing → resolved until the end condition is met:

resolved → executing → resolved → executing → resolved → ... → executed

If a recurring action fails, it stays in failed status and does not re-schedule. You can manually retry to resume the cycle.

End conditions

end_typeBehavior
neverRepeats indefinitely until cancelled
countStops after max_occurrences total executions
dateStops when the next scheduled time would be after end_date

Units

UnitMeaning
mMinutes (minimum: 5)
hHours
dDays
wWeeks
MMonths

Recurring approvals

Approval-mode actions can also recur. Each occurrence resets the gate -- recipients must respond again for each cycle.

Idempotency keys

Use idempotency_key to prevent duplicate actions when your system retries requests or a user double-clicks.

{
"idempotency_key": "trial-end-user-42",
"schedule": { "wait": "14d" },
"request": { "url": "https://api.example.com/expire" }
}

Key format patterns:

trial:user:123
deploy:v2.1
invoice:1234:reminder
weekly-report:2026-W07

Keys are scoped per account, up to 255 characters, and case-sensitive.

Behavior when a matching key already exists:

If an action with the same idempotency key already exists (in any status), the request is rejected with a 422 validation error. Idempotency keys are permanent and cannot be reused, even after the original action reaches a terminal state.

To schedule a new action that replaces an existing one, use dedup keys with coordination.on_create: "replace_existing" instead.

Dedup keys

Dedup keys group related actions together and control how they interact. Unlike idempotency keys (which prevent duplicates of the same request), dedup keys coordinate behavior across different actions that share a logical group.

{
"dedup_keys": ["deploy:api-service"],
"coordination": {
"on_create": "replace_existing"
},
"schedule": { "wait": "1h" },
"request": { "url": "https://ci.example.com/deploy" }
}

on_create behaviors

Controls what happens when you create a new action with the same dedup key as an existing non-terminal action.

BehaviorDescription
skip_if_existsReturn the existing action instead of creating a new one (response includes meta.skipped: true)
replace_existingCancel all existing non-terminal actions with matching keys, then create the new action

on_execute behaviors

Controls what happens at execution time based on other actions sharing the same dedup key. This is a nested object with condition-based logic:

{
"dedup_keys": ["deploy:api-service"],
"coordination": {
"on_execute": {
"condition": "skip_if_previous_pending",
"on_condition_not_met": "cancel",
"reschedule_delay": 300,
"max_reschedules": 10
}
}
}
FieldDescription
conditionskip_if_previous_pending, execute_if_previous_failed, execute_if_previous_succeeded, or wait_for_previous
on_condition_not_metcancel, reschedule, or fail
reschedule_delaySeconds to wait before retrying when rescheduled (used with reschedule)
max_reschedulesMaximum number of reschedule attempts (used with reschedule)

Format rules

  • Alphanumeric characters plus _, :, ., -
  • No spaces, slashes, or special characters
  • Case-sensitive (Deploy:API and deploy:api are different keys)
  • Maximum 10 keys per action
  • Scoped to your account

Valid: deploy:production, user:42:notifications, workflow.onboarding.step-1

Invalid: key with spaces, key/with/slashes, key@email.com