# Cron Jobs

> Run recurring and scheduled tasks

When you need to run periodic and recurring tasks, Encore.rs lets you declare cron jobs in code. When a job is due, Encore sends a `POST` request for the job's path to the service that declares it. Whatever the service serves at that path is the job. Encore handles the scheduling, monitoring and execution, so there's no scheduler for you to run.

> **Note**
>
> Cron jobs are not scheduled by `encore run` or `encore test`: a job runs where the app is deployed. To try one locally, [run it by hand](#running-a-job-by-hand).

## Defining a cron job

Declare a job as a module-level `static` with `Job::new`, giving its name, the path it requests, and its schedule:

```rust
use encore::cron::{Job, Schedule};

/// Removes the sessions nobody has used in a month.
static CLEANUP: Job =
    Job::new("cleanup", "/internal/cleanup", Schedule::Cron("0 4 * * *")).title("Nightly cleanup");

static REFRESH: Job = Job::new("refresh-rates", "/internal/rates", Schedule::Every("30m"));
```

`.title(…)` is optional: it's the name people see for the job, in place of its name.

The name is a unique ID for the job. If you later move the declaration to another file, Encore uses the name to know it's the same job, not a new one.

## Serving the job

A job's path is served by your web framework like any other route. Keep the route and the job in step by routing the job's own path, `JOB.path()`:

```rust
use axum::http::StatusCode;
use axum::routing::post;
use axum::Router;

async fn cleanup() -> StatusCode {
    // Remove the stale sessions…
    StatusCode::OK
}

let app = Router::new().route(CLEANUP.path(), post(cleanup));
```

The request is a `POST` with no body. Make the handler [idempotent](https://en.wikipedia.org/wiki/Idempotence#Computer_science_meaning): under certain network conditions, a job may be requested more than once for the same run.

## The rules

Encore reads the declaration from your source code, so:

* A job must be a module-level `static`, declared inside a service: its path is requested of the service that declares it.
* The name and the path must be string literals, and the schedule must be written out in the declaration, as `Schedule::Cron("…")` or `Schedule::Every("…")` with a string literal.
* The name is lowercase letters, digits and dashes, starting with a letter, and unique in the app.
* The path starts with a `/`.
* The only option is `.title(…)`, with a string literal.

Encore reports a declaration that breaks these rules when it parses your app, for example on `encore run`.

## Schedules

### Every

`Schedule::Every` runs a job at a fixed interval, written as a duration of hours and minutes: `"30m"`, `"6h"`, `"1h30m"`.

The runs are laid out over the day from midnight UTC, the same every day: `"6h"` runs at 00:00, 06:00, 12:00 and 18:00. So that the interval between runs stays the same, including from the last run of one day to the first of the next, the interval:

* is a whole number of minutes, at most 24 hours, and
* **divides 24 hours evenly**: `"45m"` and `"8h"` do, `"50m"` and `"7h"` do not.

Encore reports an interval that doesn't, and suggests the nearest one that does.

### Cron expressions

For schedules an interval can't express, such as a specific day of the month or week, use `Schedule::Cron` with a [cron expression](https://en.wikipedia.org/wiki/Cron#Cron_expression). It has five fields — minute, hour, day of month, month and day of week — and all times are in UTC:

```rust
// At 04:00 UTC on the 15th of each month.
static ACCOUNTING_SYNC: Job = Job::new(
    "accounting-sync",
    "/internal/accounting-sync",
    Schedule::Cron("0 4 15 * *"),
);
```

Each field is `*` (any), a value, a range (`9-17`), or a comma-separated list of them (`0,30`), each optionally with a step (`*/15`, `0-30/10`). Months and weekdays can be written by name (`JAN`–`DEC`, `SUN`–`SAT`, in any case), and Sunday is `0`. Some examples:

| Expression | Runs |
| --- | --- |
| `0 4 * * *` | 04:00 every day |
| `*/15 * * * *` | Every 15 minutes |
| `0 9 * * 1` | 09:00 on Mondays |
| `0 9-17 * * MON-FRI` | On the hour, 09:00 to 17:00, on weekdays |

## Running a job by hand

Nothing runs a job locally on its own. To run one, do what Encore does when it's due: send a `POST` for its path to the service, while `encore run` is running:

```bash
curl -X POST localhost:<port>/internal/cleanup
```

In tests, call the function that serves the path.

## Keep in mind

- Cron jobs don't run during local development or under `encore test`.
- A job's handler takes no input: the request has no body.
