# Scheduled tasks

A scheduled task gives an agent a job on every date of a cron. The agent works on it alone, writes first by email to one contact, can attach a file that one of its own HTTP actions returns, and keeps the conversation by email if the contact answers. Use it for a monthly report, a reminder with a statement, or any message that has to go out on a date without a person starting it.

## Create a task

The agent must be active, created through the API or a template, and have the `email` channel. The sender domain must be verified (see [Write by email](/guides/email)). Your key needs `schedules:write`.

```bash title="cURL"
curl -X POST "https://api.lelemon.cl/v2/agents/agt_5RvP2aKq9XwLmN3tYbZc01/schedules" \
  -H "Authorization: Bearer $LELEMON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Monthly sales report",
    "cron": "0 9 2 * *",
    "timezone": "America/Santiago",
    "goal": "Send the sales report of the month to the contact.",
    "contact": { "externalRef": "customer-8841" },
    "channel": "email",
    "from": "reports@acme.com",
    "onReply": { "instructions": "If the contact answers, help them read the report." }
  }'
```

It answers `201` with `nextRunAt`, the next date in UTC. `?dryRun=true` runs the same checks and shows the task without saving it.

| Field | Meaning |
| --- | --- |
| `cron` | Five fields: minute, hour, day of month, month, day of week. `422 invalid_cron` if it cannot be read. |
| `timezone` | An IANA name. It defaults to `America/Santiago`. `422 invalid_timezone` otherwise. |
| `goal` | What the agent has to do on each date, up to 8,000 characters. |
| `contact` | `contactId` or `externalRef`, exactly one. The contact needs an email address. An email address is not accepted here. |
| `from` | The sender. Its domain must be verified. Without it, the first verified domain is used. |
| `onReply` | `instructions` the agent gets if the contact answers. The same agent keeps the conversation by email. |
| `limits` | `maxRounds` and `maxMinutes` of each run, cut to the same maximums as [a run](/guides/runs). |

An agent can have up to 20 tasks and an organization up to 1,000: `409 schedule_limit_reached` after that.

## What happens on each date

On the date Lelemon starts one [run](/guides/runs) of the agent with your `goal` and the contact. The run has `schedule` set to the id of the task, and you can list them with `GET /v2/runs?scheduleId=sch_…`. A date that was missed because the service was down is not replayed: the task fires once and moves to its next date.

The run is unattended. The agent never stops to ask a person, so write a goal it can finish alone. To write to the contact it uses the `email_contact` tool, which you grant like any other tool of the agent:

- `subject` and `text` of the email. The agent cannot choose another recipient: the email always goes to the contact of the task.
- `attachments`, up to three. Each one names an HTTP action already granted to that agent (`tool`), what the action needs (`input`) and the name of the file (`filename`). Lelemon calls the action as it would for the agent, reads the body as the file and attaches it. Only `.csv`, `.pdf`, `.xlsx` and `.txt`, up to 5 MB each, and the content has to be what the extension says. If one attachment fails nothing is sent and the agent sees why.

The first email goes to the case of that agent with that contact. Two agents writing to the same contact keep two separate conversations, and each answer reaches the agent that wrote.

If the contact opted out of email or is outside the [recipient allowlist](/guides/email), no run starts and you get `schedule.skipped`.

## Events

| Event | When |
| --- | --- |
| `schedule.fired` | A date arrived and a run started. It carries the schedule and the run. |
| `schedule.skipped` | A date arrived and no run started. `reason` says why. |
| `schedule.paused` | Three runs in a row failed or were cut, so the platform paused the task. Turn it on again with `PATCH /v2/schedules/{id}` and `"enabled": true`. |

## Change, pause and delete

`PATCH /v2/schedules/{id}` changes `name`, `cron`, `timezone`, `goal`, `contact`, `limits` and `enabled`. A new cron or timezone recomputes `nextRunAt` from now. `DELETE /v2/schedules/{id}` answers `204`: it stops firing, and the runs it already started stay readable.

## Fire a task now

`POST /v2/schedules/{id}/fire` starts one run right now, as the clock would, so you can try a task without waiting for its date. It sends a real email to the contact, so point the task at a contact of yours while you try it. It does not move `nextRunAt` and does not use up the next real date, and you can call it as many times as you need. It needs `schedules:write` and an `Idempotency-Key`, and a paused task can be fired too.
