> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lithoblocks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Recurring messages

> Repeat a scheduled message at a fixed interval, end the series by count or date, and stop it early.

A scheduled message repeats when you give it a `recurrence`. Each occurrence is a normal
[scheduled message](/guides/schedule-a-message): it is sent by the evaluator, costs 2 credits,
and appears in the list and in the execution log on its own.

## Add a recurrence

```bash theme={null}
curl -X POST https://api.lithoblocks.com/v1/scheduled-messages \
  -H "Authorization: Bearer $LITHOBLOCKS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "6b1e…",
    "data": { "topic": "Daily stand-up" },
    "destination": { "channel_id": "C0AS92CQ04D" },
    "schedule": { "send_at": "2026-09-14T09:00:00+02:00", "timezone": "Europe/Brussels" },
    "recurrence": { "interval_minutes": 1440, "max_repeats": 10, "end_at": "2026-12-31T23:59:59Z" }
  }'
```

| Field              | Required | Meaning                                                                           |
| ------------------ | -------- | --------------------------------------------------------------------------------- |
| `interval_minutes` | yes      | Minutes between occurrences, a positive integer. `1440` is daily, `10080` weekly. |
| `max_repeats`      | no       | How many further occurrences follow the first.                                    |
| `end_at`           | no       | ISO 8601 with an offset; no occurrence is scheduled after it.                     |

`max_repeats` and `end_at` are both optional and whichever comes first ends the series. With
neither, the series runs until you cancel it.

## How the series runs

Only one occurrence exists ahead of time. When the evaluator sends one, it creates the next as a
new scheduled message with its own `id`, `repeat_count` one higher and `source: "repeat"`; the
one just sent stays in the list as `sent`. So at any moment the series has exactly one row with
`status: "scheduled"`, and that row is the one to act on.

The next time is computed from the previous **scheduled** time, not from when the send actually
happened: `send_at + interval_minutes`. A tick that fires a minute late therefore does not push
every later occurrence a minute later. The series ends when `repeat_count` reaches `max_repeats`
or the next time would fall after `end_at`.

An occurrence that fails to send, or that expires under its
[delivery conditions](/guides/delivery-conditions), does not create a next one. The series stops
there, and the failed or expired row carries the reason in `error_message`.

Everything else on the message travels with it: `data`, `destination`, `schedule.timezone` and
`delivery_conditions` are copied onto each new occurrence, and a `max_delay_at` deadline is
recomputed for each one from its own time. A `PATCH` to the scheduled occurrence therefore
changes the rest of the series as well.

## Stop it

Cancel the occurrence that is currently `scheduled`:

```bash theme={null}
curl -X DELETE https://api.lithoblocks.com/v1/scheduled-messages/{id} \
  -H "Authorization: Bearer $LITHOBLOCKS_API_KEY"
```

Nothing further is created, and the occurrences already sent stay as they are. To keep the next
occurrence but stop repeating after it, `PATCH` it with `"recurrence": null`.

To find the series, list with `?template_id=` and read `repeat_count`; the occurrence with
`status: "scheduled"` is the live one. On the web app's Scheduled page a recurring message reads
as, for example, "Every 1 day, 10×, until 2026-12-31".

## Fixed times of day

Recurrence is an interval, not a calendar rule; "weekdays at 09:00" is not a shape the API takes.
Combine a daily interval with a time-window condition instead: an occurrence that lands on a
Saturday is held until Monday's window opens, and later occurrences still count from the
scheduled time, so they stay on the daily grid.

```json theme={null}
"schedule": { "send_at": "2026-09-14T09:00:00+02:00", "timezone": "Europe/Brussels" },
"recurrence": { "interval_minutes": 1440 },
"delivery_conditions": {
  "conditions": [{ "type": "time_window", "start": "09:00", "end": "10:00", "days": [1, 2, 3, 4, 5] }]
}
```

Daylight-saving changes shift the wall-clock time by an hour, because the interval is a fixed
number of minutes. A one-hour window absorbs that; a narrower one may not, and the message would
then wait for the next day's window.
