Cron Jobs
Run recurring and scheduled tasks
When you need to run periodic and recurring tasks, Encore.py lets you declare cron jobs in code. Encore takes care of scheduling them where your app is deployed, and you can monitor their runs in the Encore Cloud dashboard.
A cron job is a scheduled HTTP request: when the job is due, Encore sends a POST request for the job's path to the service that declares it. Your web framework handles that request like any other.
users/main.pyfrom encore import cron
from fastapi import FastAPI
app = FastAPI()
# Send a welcome email to everyone who signed up in the last two hours.
_ = cron.Job(
"welcome-email",
path="/internal/welcome-emails",
title="Send welcome emails",
every="2h",
)
@app.post("/internal/welcome-emails")
async def send_welcome_emails() -> None:
# Email everyone who signed up recently. Idempotent: each person
# gets at most one welcome email.
...
Cron jobs don't run when you develop locally with encore run, or under encore test. To try one out, request its path yourself: see Running a job manually.
Declaring a cron job
Create a job with cron.Job, assigned to a module-level variable. Since you won't use the variable, _ is a good name for it.
from encore import cron
_ = cron.Job("accounting-sync", path="/internal/accounting-sync", schedule="0 4 15 * *")
The arguments are:
- The job's name: a unique kebab-case ID such as
welcome-email. If you later move the declaration to another file, Encore uses the name to know it's the same job. path: the path to request, starting with/.title: an optional human-readable title, shown in the dashboard in place of the name.- Exactly one of
everyandschedule, which say when the job runs. See Schedules.
Encore reads the declaration from your source code to schedule the job, so:
- Declare it as a module-level variable, not inside a function, class, or
ifblock, and not as a bare expression without an assignment. - Write the name and every option as string literals.
- Declare it inside a service, since the path is requested of the service that declares it.
cron.Job also raises ValueError at import time if both or neither of every and schedule are set, or if path doesn't start with /.
Handling the request
When the job is due, Encore sends a POST request for path to the declaring service. Serve that path with your web framework, as with the FastAPI route above. A Django or Flask view works the same way, as long as it accepts POST.
Keep in mind:
- The path is an ordinary route of your service, reachable by whatever can reach the service. If it's exposed publicly, consider whether anyone calling it could cause harm.
- Make the handler idempotent: in rare cases, such as network failures, a job may be run more than once.
- The path must be written out in both places, since the job's
pathhas to be a string literal.
Schedules
Running at an interval
every runs the job at a fixed interval, as a duration string such as "10m", "6h" or "1h30m" (units s, m, h and d).
The runs are laid out over each day starting at midnight UTC, the same every day: every="6h" runs at 00:00, 06:00, 12:00 and 18:00 UTC. So that the delay between runs is always the same, the interval must be a whole number of minutes that divides 24 hours evenly, and at most 24 hours. "10m", "45m" and "8h" are fine; "7h" and "50m" aren't. Encore reports an invalid interval when it parses your app, suggesting the nearest one that works.
Cron expressions
For schedules an interval can't express, such as a specific day of the month or week, use schedule with a cron expression instead of every. All times are in UTC.
# Run the monthly accounting sync at 04:00 UTC on the 15th day of each month.
_ = cron.Job(
"accounting-sync",
path="/internal/accounting-sync",
title="Monthly accounting sync",
schedule="0 4 15 * *",
)
An expression has the standard five fields: minute (0–59), hour (0–23), day of month (1–31), month (1–12) and day of week (0–6, with Sunday as 0). Each field is *, a value, a range such as 9-17, or a comma-separated list of these, each optionally with a step such as */15. Months and weekdays can also be written by name, such as JAN or mon-fri. Shorthands such as @daily aren't supported.
Running a job manually
Nothing schedules cron jobs locally, so to exercise one, send the request Encore would send: a POST for the job's path to the service, while encore run is running. For example, with curl against the service's local URL:
$ curl -X POST http://localhost:<port>/internal/welcome-emails
If you use an AI agent connected to Encore's MCP server, it can run a job by name with the cron_run tool, which sends the same request through Encore's local service proxy.
In tests, call the function that serves the path, or request the path with your framework's test client.