> ## 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.

# Delivery conditions

> Hold a scheduled message for business hours, set a deadline, and decide what happens when it passes.

`schedule.send_at` is the earliest a [scheduled message](/guides/schedule-a-message) can go.
`delivery_conditions` can hold it past that moment until the conditions are met, up to a
deadline you choose.

```json theme={null}
"delivery_conditions": {
  "conditions": [
    { "type": "time_window", "start": "09:00", "end": "17:00", "days": [1, 2, 3, 4, 5], "timezone": "Europe/Brussels" }
  ],
  "max_delay_minutes": 480,
  "on_miss": "expire"
}
```

| Field               | Required | Meaning                                                                                                                                                                      |
| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `conditions`        | no       | A list of conditions; all of them must hold at the same time for the message to send. Empty means no hold.                                                                   |
| `max_delay_minutes` | no       | How long past `send_at` the message may wait. Without it the message waits until the conditions are met, however long that takes.                                            |
| `on_miss`           | no       | What to do when the deadline passes with the conditions still unmet: `send` anyway (the default) or `expire` without sending. Required when a presence condition is present. |

Once the message is due, the evaluator checks the conditions every minute. While they hold it
back the status stays `scheduled`, and each check is recorded in `GET /v1/queue-executions` as a
`constraint_check` with `result: "deferred"` and the reasons in `error_message`, for example
`outside time window 09:00–17:00 (Europe/Brussels)`.

## Time windows

```json theme={null}
{ "type": "time_window", "start": "09:00", "end": "17:00", "days": [1, 2, 3, 4, 5], "timezone": "Europe/Brussels" }
```

| Field          | Required | Meaning                                                                                                                           |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `start`, `end` | yes      | Local times as `HH:MM`, 24-hour. The window includes `start` and ends just before `end`.                                          |
| `days`         | no       | Days of the week on which the window applies: `0` is Sunday, `6` is Saturday. The default is Monday to Friday, `[1, 2, 3, 4, 5]`. |
| `timezone`     | no       | The IANA zone the window is read in.                                                                                              |

The zone is the condition's own `timezone`, otherwise the message's `schedule.timezone`,
otherwise UTC. A window may cross midnight: `"start": "22:00", "end": "06:00"` covers the evening
of a listed day and the small hours that follow it.

## The deadline

`max_delay_at` in the response is `send_at + max_delay_minutes`, the moment the wait ends. It is
`null` when there is no `max_delay_minutes`. The evaluator recomputes it when you reschedule,
and each occurrence of a [recurring message](/guides/recurring-messages) gets its own.

When `max_delay_at` passes and the conditions still do not hold:

* `on_miss: "send"`, or no `on_miss` at all: the message is sent at that tick regardless.
* `on_miss: "expire"`: the message becomes `expired`, a terminal status, with `error_message`
  starting `expired:` followed by the reasons, and nothing is sent. The execution log records a
  `constraint_check` with `result: "failed"`.

A message that is `expired` cannot be rescheduled or sent; schedule a new one.

## Changing the conditions

`PATCH /v1/scheduled-messages/{id}` with a new `delivery_conditions` replaces the whole object;
with `"delivery_conditions": null` it removes the hold. `POST …/send-now` removes the conditions
and the deadline as well as making the message due, so it goes on the next tick.

## Presence conditions (reserved)

The contract also names a `presence` condition: deliver when the recipient is active on Slack.

```json theme={null}
{ "type": "presence", "target": "recipient", "mode": "active" }
{ "type": "presence", "target": { "slack_user_id": "U0123ABCD" }, "mode": "after_active_minutes", "minutes": 15 }
```

| Field     | Meaning                                                                                                                |
| --------- | ---------------------------------------------------------------------------------------------------------------------- |
| `target`  | `"recipient"` for the message's destination user, or `{ "slack_user_id": "U…" }` for another user.                     |
| `mode`    | `active`: send while the target is active. `after_active_minutes`: send once the target has been active for `minutes`. |
| `minutes` | Required with `after_active_minutes`.                                                                                  |

When a presence condition is present, `on_miss` is required: say whether the message should
`send` or `expire` if the target never becomes active before the deadline.

Presence delivery is not enabled yet. The API validates the shape but answers `422` with
`Presence delivery not yet enabled` on create and on `PATCH`; remove the condition to schedule
the message. The shape is fixed now so that enabling it later changes nothing in what you send.
