Schedules Concepts
This page explains what happens between declaring a schedule and your code running. Read it before you rely on a schedule for work where timing or duplicates matter.
Each time a schedule's expression matches, Edgenivo creates a run for that occurrence:
- Edgenivo picks a start time within the schedule's jitter window.
- Edgenivo delivers the run to the schedule's target on the current deployment of the schedule's environment or branch. This isn't necessarily the deployment that first declared the schedule.
- Your function, route, or queue consumer does the work.
Every run has an execution ID. It stays the same for every delivery of that occurrence, so you can use it to detect duplicates. Every run also has a scheduled time, the time the expression matched. The scheduled time doesn't include jitter.
Runs are independent. A slow run doesn't delay the next one, so two runs of the same schedule can overlap.
Edgenivo keeps a separate set of schedules for production, each custom environment, each Preview branch, and CLI deployments by each user. A CLI user environment can also include the Git branch reported by the deployment. Changing a schedule in one environment doesn't affect the others.
Edgenivo applies a deployment's static schedules when that deployment becomes the current deployment of its environment or branch. This includes promotions and rollbacks: rolling back production restores the schedules from the deployment you roll back to.
Static schedules start in different states depending on the environment:
| Environment | Initial state of a new static schedule |
|---|---|
| Production | Active |
| Preview branches | Inactive |
| Custom environments | Inactive by default. You can change this per environment. |
Inactive schedules don't run until you enable them, so a Preview branch can't run a production job by accident. Once you enable or disable a static schedule, it keeps that state across later deployments. Changing a custom environment's default doesn't change the state of schedules that already exist.
When your code calls @vercel/schedules, the SDK uses the environment or branch of the deployment that runs the code. A Preview deployment on the feature-x branch creates, lists, and changes schedules for feature-x only.
The vercel schedules CLI manages production schedules by default. Pass --environment for a custom environment, --git-branch for a Preview branch, --user for CLI deployments by a Edgenivo user ID, or --local for a vercel dev session. You can combine --user and --git-branch. See Choose an environment.
Jitter spreads runs across a short window instead of starting every schedule at the same second. A schedule set for 09:00 with a 5-minute jitter window starts at some point between 09:00 and 09:05.
| Plan | Jitter window |
|---|---|
| Hobby | Always 15 minutes |
| Pro and Enterprise | 1 to 15 minutes. Defaults to 1 minute. |
The shortest interval between a cron schedule's runs must be at least as long as its jitter window. For example, a schedule that runs every 5 minutes can use a jitter window of up to 5 minutes. Because Hobby always uses a 15-minute window, Hobby cron schedules can run at most once every 15 minutes.
To set the window, use jitter in vercel.json, the SDK, or the CLI.
Each schedule evaluates its expression in an IANA timezone, such as America/New_York. The default is UTC.
Named timezones follow daylight saving time. A schedule set for 0 9 * * * in America/New_York runs at 09:00 New York time all year, which is 13:00 UTC in summer and 14:00 UTC in winter.
A one-time schedule runs once at a local date and time, such as 2026-10-01T09:00, in the schedule's timezone. You can only create one-time schedules as dynamic schedules, with the SDK or the CLI.
After a one-time schedule's scheduled time passes:
- Its state stays
active. There's no separate completed state. - You can't enable, disable, or update it.
- You can delete it once its run has been dispatched. While the run is still being dispatched, for up to five jitter windows after the scheduled time, deleting it fails with
schedule_single_at_passed. - Edgenivo removes it about 30 days after its scheduled time, and it stops counting toward your team's schedule limit as soon as its run is dispatched.
You can still run it manually before its scheduled time.
A schedule is either active or inactive. Only active schedules run on their cadence. You can run any schedule manually, whatever its state.
You can also enable a schedule until a set time. While that window is open, the schedule is active and has an activeUntil timestamp. When the time passes, the schedule becomes inactive and activeUntil is cleared. Edgenivo keeps the schedule, so you can enable it again later. Use this to run a Preview schedule for a few hours while you test it, without leaving it on.
If Edgenivo can't deliver a run on time, it delivers the run late. Each occurrence has an expiry. If Edgenivo still can't deliver the run when the occurrence expires, Edgenivo skips it.
An occurrence expires after its jitter window plus the shortest interval between the schedule's runs. Edgenivo counts that interval as at least 10 minutes, so a short delay doesn't make a frequent schedule skip runs.
These runs never expire:
- One-time schedules
- Runs you start manually from the dashboard, SDK, or CLI
- Cron schedules that set a day of the month or a month, such as monthly or yearly schedules
Edgenivo uses the shortest interval between runs, not the time until the next run. For 0 9 * * 1-5 (weekdays at 09:00), the shortest interval is one day. Friday's occurrence expires on Saturday at 09:01, even though the next run is on Monday.
When delivery resumes, Edgenivo delivers every occurrence that hasn't expired. A frequent schedule can run several times in quick succession. For example, an every-minute schedule delayed for 2 hours runs up to 12 times when delivery resumes, once for each occurrence from the last 11 minutes.
If your task should only run close to its scheduled time, compare the scheduled time to the current time and return early.
Edgenivo delivers each run once in normal operation, but it can't rule out a duplicate delivery. Make your handler safe to run more than once. Use the execution ID to detect a run you've already processed. See Make your handler safe to run more than once.
How Edgenivo handles a failed run depends on the target:
| Target | Retries |
|---|---|
| Function | No. Edgenivo doesn't retry a run if your function throws, returns an error, or times out. |
| Path | No. Edgenivo doesn't retry a run if your route returns an error or times out. |
| Queue topic | Yes. The queue retries delivery to your consumer until it succeeds or the message expires. See Queues retries. |
If a failed run must be retried, target a queue topic.
Was this helpful?