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

# Send-window optimization with send_at_type

> Treat scheduled_at as the earliest acceptable time instead of an exact instant: per-message and org-level send windows, the shifted fire time, the fail-closed behavior, and when to use each mode.

# Send-window optimization with `send_at_type`

Every send endpoint that accepts `scheduled_at` also accepts an optional `send_at_type` field. With the default (`"fixed"` or omitted), the platform honors your `scheduled_at` instant verbatim. With `"optimize_with_send_window"`, the platform treats `scheduled_at` as the **earliest acceptable send time** and, if that instant falls outside your configured send window in the recipient's local timezone, moves the fire time forward to the next window open.

This page covers the two modes, where the send window comes from, a worked end-to-end example, how the shift is stored, what happens when the platform cannot read your window, and when to pick each mode. For the scheduling model itself — park, gate at fire time, fire — see [Schedule one-off sends with `scheduled_at`](/guides/message-scheduling).

## `fixed` vs `optimize_with_send_window`

| Value                         | What `scheduled_at` means         | When the message fires                                                                   |
| ----------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------- |
| `"fixed"` (default)           | The exact fire time               | At `scheduled_at`, verbatim                                                              |
| `"optimize_with_send_window"` | The earliest acceptable fire time | At `scheduled_at` if it lands inside your send window; otherwise at the next window open |

Omitting `send_at_type` is identical to passing `"fixed"` — every existing scheduled send keeps its current behavior. The optimization is strictly opt-in per message.

When no send window is configured anywhere (neither on the message nor in organization settings), `"optimize_with_send_window"` is a no-op and behaves like `"fixed"`.

## Where the send window comes from

The platform resolves the window in two steps:

1. **Per-message override** — a `send_window` object inside the message's `metadata`. If present and `enabled`, it wins.
2. **Organization fallback** — the `send_window` object in your organization settings, used when the message carries no enabled override.

The window shape is the same in both places:

| Field          | Meaning                                                                                                                 |
| -------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `enabled`      | Whether this window applies.                                                                                            |
| `start_hour`   | Inclusive 24h start hour (0–23) recipient-local, e.g. `9` for 9 AM.                                                     |
| `end_hour`     | Exclusive 24h end hour (0–23) recipient-local, e.g. `20` for 8 PM. Set `start_hour > end_hour` for an overnight window. |
| `allowed_days` | Optional allowed weekdays, `0` = Sunday … `6` = Saturday. Omit to allow every day.                                      |

### Per-message window

Attach the window to the individual send:

```json theme={null}
{
  "to": "+14155552671",
  "body": "Your appointment is tomorrow at 10 AM.",
  "scheduled_at": "2026-04-16T03:00:00Z",
  "send_at_type": "optimize_with_send_window",
  "recipient_timezone": "America/New_York",
  "metadata": {
    "send_window": {
      "enabled": true,
      "start_hour": 9,
      "end_hour": 20,
      "allowed_days": [1, 2, 3, 4, 5]
    }
  }
}
```

Use this when one message needs a different envelope than the rest of your traffic — a promotional blast with tighter hours than your transactional sends, for example.

### Organization-level window

Set the fallback once via `PUT /api/v1/settings/general` (the same surface the org quiet-hours gate uses; see [Quiet hours: org-wide channel gates vs. the campaign fallback window](/guides/quiet-hours-configuration)):

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/settings/general \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "send_window": {
      "enabled": true,
      "start_hour": 9,
      "end_hour": 20,
      "allowed_days": [1, 2, 3, 4, 5]
    }
  }'
```

With the org window in place, any send that passes `send_at_type: "optimize_with_send_window"` picks it up without repeating the window on every request. A per-message `metadata.send_window` overrides the org window for that message only.

`recipient_timezone` is an IANA timezone name (`America/New_York`, `Europe/Istanbul`) that tells the platform which local clock to evaluate the window against. Pass the recipient's timezone explicitly whenever you know it.

## Worked example: 3 AM becomes 9 AM

POST to the SMS send endpoint with a fire time that lands at 3 AM in the recipient's local timezone:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/messages/sms \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155552671",
    "body": "Your appointment is tomorrow at 10 AM.",
    "scheduled_at": "2026-04-16T07:00:00Z",
    "recipient_timezone": "America/New_York",
    "send_at_type": "optimize_with_send_window"
  }'
```

With the org window above (09:00–20:00 recipient-local, weekdays): `2026-04-16T07:00:00Z` is 3:00 AM in New York on a Thursday, which is outside the window. The platform computes the next window open — 9:00 AM recipient-local, `2026-04-16T13:00:00Z` — and stores **that** as the fire time. The response is still `202` with `status: "scheduled"`.

Had the requested instant already been inside the window — say `14:00Z` (10 AM in New York) — it would be stored unchanged. The optimization only ever moves a send forward to the next open, never backward.

## One-shot shift: the stored fire time is already adjusted

The shift happens once, at schedule time. The platform computes the adjusted instant and persists it as the row's `scheduled_at`, exactly as if you had submitted that time yourself. There is no separate "requested" vs "adjusted" field and no per-fire window evaluation — the scheduler's plain due-rows selector (`scheduled_at <= now`) picks the row up with no special handling.

The practical consequences:

* `GET /api/v1/messages/scheduled` and `GET /api/v1/messages/{id}` both show the **adjusted** fire time. If you scheduled 3 AM recipient-local and the window moved it to 9 AM, the API reads 9 AM back.
* `PATCH /api/v1/messages/{id}` rewrites the (adjusted) `scheduled_at` like any other scheduled row; cancel and bulk-cancel behave identically.
* If you need the original requested instant for audit, keep it in your own logs or in `metadata` — the platform stores the adjusted value as the single source of truth for when the row fires.

## Failure mode: fail-closed when the window can't be read

`"optimize_with_send_window"` is an opt-in promise that the message will not fire outside your window. If the platform cannot read your organization settings — a transient database fault, for example — it does **not** silently fall back to `"fixed"` and ship a 3 AM send. Instead the send request fails with a `503` and code `SEND_WINDOW_LOOKUP_FAILED`, asking you to retry. No scheduled row is persisted, so nothing fires out-of-window and nothing is lost — retry the request and it succeeds once settings are readable again.

Note the asymmetry: a *missing* window (no per-message override, org window unset or `enabled: false`) is a no-op; an *unreadable* settings row is a 503. The first means "no optimization configured," the second means "optimization requested but not verifiable."

## When to use each mode

**Use `optimize_with_send_window` for compliance-sensitive marketing traffic.** Non-transactional SMS is commonly restricted to daytime recipient-local hours (the TCPA treats 8 AM–9 PM recipient-local as the solicitation envelope in the US, and similar smile-hours rules exist elsewhere). Configure the org window once, keep passing your natural "send not before" time, and let the platform absorb the timezone arithmetic per recipient. This is a tenant-owned control: the window you configure is yours; the platform applies it but does not mandate it.

**Use `fixed` when the clock time is the product.** Appointment reminders, two-factor codes, read-back-a-code flows, and any message whose value depends on landing at a precise instant. An appointment reminder optimized to a 9–20 window could silently slide to the next morning — for that class of message, verbatim timing is the correct behavior and the window offers nothing.

The two modes compose per send: a tenant can run marketing blasts with optimization and transactional traffic with fixed timing on the same account, at the same time.

## Related

* [Schedule one-off sends with `scheduled_at`](/guides/message-scheduling) — the base scheduling surface this field extends.
* [The scheduled-sending model](/concepts/scheduled-sending-model) — park → gate at fire → fire → bill once.
* [Quiet hours: org-wide channel gates vs. the campaign fallback window](/guides/quiet-hours-configuration) — the org-wide gate that blocks sends outright, distinct from this per-message shift.
* [Messaging API reference](/api-reference/endpoints/messaging) — request/response shapes for every send endpoint.
