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.
fixed vs optimize_with_send_window
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:- Per-message override — a
send_windowobject inside the message’smetadata. If present andenabled, it wins. - Organization fallback — the
send_windowobject in your organization settings, used when the message carries no enabled override.
Per-message window
Attach the window to the individual send:Organization-level window
Set the fallback once viaPUT /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):
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: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’sscheduled_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/scheduledandGET /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_atlike 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
Useoptimize_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— the base scheduling surface this field extends. - The scheduled-sending model — park → gate at fire → fire → bill once.
- Quiet hours: org-wide channel gates vs. the campaign fallback window — the org-wide gate that blocks sends outright, distinct from this per-message shift.
- Messaging API reference — request/response shapes for every send endpoint.