> ## 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 later — schedule an Inbox reply end to end

> Compose a reply now and have it dispatched at a future time: schedule it, list what's pending, watch it promote to sent, and cancel before dispatch. Covers the 35-day horizon, contact resolution, and when a macro or a campaign is the better tool.

# Send later — schedule an Inbox reply end to end

Send later lets an agent write a reply to a conversation now and have it dispatched at a future timestamp, without building a macro or a campaign. The reply parks on the scheduled-outbound pipeline until the dispatcher picks it up, and you can list what's pending or pull it back at any point before it fires. This guide walks the feature end to end: when it applies, the three endpoints behind it, the validation rules that decide what gets accepted, and a worked example you can paste.

<Steps>
  <Step title="Know what send later does">
    Compose now, dispatch later, parked on the scheduled outbound pipeline.
  </Step>

  <Step title="Schedule, list, cancel — the three endpoints">
    POST to schedule, GET to list pending, DELETE to cancel before dispatch.
  </Step>

  <Step title="Know when it isn't a macro">
    Standalone single-shot vs the macro's scheduleFollowUp step.
  </Step>

  <Step title="Front-load the validation rules">
    Future timestamp, inside the 35-day horizon, body within limits.
  </Step>

  <Step title="Handle the no-destination failure">
    A conversation with no contact phone or email rejects with 422.
  </Step>

  <Step title="Understand the channel behavior">
    Defaults to the conversation's channel; routed by your standard sender setup.
  </Step>

  <Step title="Run the worked example (SMS and email)">
    Schedule a reply, list pending, watch it promote, cancel before dispatch.
  </Step>

  <Step title="Know when NOT to use send later">
    Cadence belongs in macros; bulk and drip belong in campaigns.
  </Step>
</Steps>

## 1. What send later does

Send later is an Inbox composer affordance, not a separate sending system. The flow:

1. An agent composes the reply exactly as they would for an immediate send.
2. Instead of dispatching now, the reply is stored with a future `scheduled_at` timestamp and a `scheduled` status.
3. The scheduled-message dispatcher fires roughly once a minute and promotes any reply whose `scheduled_at` has passed, sending it through the same channel path an immediate reply would take.
4. Until dispatch, the reply is fully visible and fully cancellable: list the pending replies on the conversation, delete one to pull it back.

Because the reply rides the standard scheduled-outbound pipeline, anything your workspace can send immediately — SMS, WhatsApp, email, RCS, Viber, and the rest of the Inbox channel set — can be sent later, with an optional media attachment for MMS and WhatsApp.

**It's your content, your timestamp.** Send later is a tenant-owned control: nothing outside your workspace reads or alters a reply you scheduled, and a scheduled reply only ever goes to a contact already on the conversation. Whether the reply clears consent and quiet-hours gates at dispatch is decided by the same tenant-configured checks an immediate send passes through.

**Skip conditions for this guide.** If you want a multi-step sequence (send, then close, then tag), use a macro with a scheduleFollowUp step — the section below tells you which. If you want the same message to a list of contacts, use a campaign. Send later is the single-shot case: one reply, one customer, one timestamp.

## 2. The three endpoints

Everything the composer's Send later affordance does routes through three endpoints under the Inbox API. You can drive the same behavior from an integration.

### Schedule a reply

`POST /api/v1/inbox/conversations/{convId}/scheduled-replies`

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/conversations/conv_4f2c91ab/scheduled-replies" \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "body": "Good morning — your order shipped overnight, tracking 1Z84A9026.",
      "scheduled_at": "2026-09-27T09:00:00Z"
    }'
  ```

  ```typescript Node.js theme={null}
  const res = await fetch(
    "https://api.orbit.devotel.io/api/v1/inbox/conversations/conv_4f2c91ab/scheduled-replies",
    {
      method: "POST",
      headers: {
        "X-API-Key": process.env.ORBIT_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        body:
          "Good morning — your order shipped overnight, tracking 1Z84A9026.",
        scheduled_at: "2026-09-27T09:00:00Z",
      }),
    },
  );
  console.log(await res.json());
  ```
</CodeGroup>

The **request body** is the only input:

| Field             | Required | Description                                                                                                                                                                                                          |
| ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `body`            | Yes      | Reply text, 1–8,000 characters. An empty body rejects.                                                                                                                                                               |
| `scheduled_at`    | Yes      | ISO-8601 timestamp, in the future, at most **35 days** out.                                                                                                                                                          |
| `channel`         | No       | One of the Inbox channel slugs (`sms`, `whatsapp`, `email`, `rcs`, `viber`, `voice`, `fax`, `instagram`, `messenger`, `line`, `telegram`, `mms`, `wechat`, `kakao`, `zalo`). Defaults to the conversation's channel. |
| `media_url`       | No       | Media attachment URL for an MMS or WhatsApp media reply.                                                                                                                                                             |
| `source_macro_id` | No       | A macro id to attribute the scheduled reply to a macro in analytics.                                                                                                                                                 |

A successful schedule returns **201** with the parked row:

```json theme={null}
{
  "data": {
    "id": "msg_0c9e2a7b1f4d3e5a",
    "conversation_id": "conv_4f2c91ab",
    "channel": "sms",
    "status": "scheduled",
    "scheduled_at": "2026-09-27T09:00:00Z"
  },
  "meta": { "request_id": "req_sched_create", "timestamp": "2026-09-26T14:02:00Z" }
}
```

### List pending scheduled replies

`GET /api/v1/inbox/conversations/{convId}/scheduled-replies`

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.orbit.devotel.io/api/v1/inbox/conversations/conv_4f2c91ab/scheduled-replies" \
    -H "X-API-Key: dv_live_sk_your_key_here"
  ```

  ```typescript Node.js theme={null}
  const res = await fetch(
    "https://api.orbit.devotel.io/api/v1/inbox/conversations/conv_4f2c91ab/scheduled-replies",
    { headers: { "X-API-Key": process.env.ORBIT_API_KEY } },
  );
  console.log(await res.json());
  ```
</CodeGroup>

The list contains only replies that are still `scheduled` — dispatched and cancelled rows drop out. Entries are ordered by `scheduled_at`, earliest first, and the server caps the response at **50 pending replies per conversation**, with the cap echoed back as `limit` so you know when it bit. Each entry carries the recipient, body, media URL, and both author and macro attribution (`scheduled_by_user_id`, `source_macro_id`). Point your "Pending sends" drawer at this endpoint; it polls happily on a 30-second cadence.

### Cancel a pending scheduled reply

`DELETE /api/v1/inbox/scheduled-replies/{id}`

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE "https://api.orbit.devotel.io/api/v1/inbox/scheduled-replies/msg_0c9e2a7b1f4d3e5a" \
    -H "X-API-Key: dv_live_sk_your_key_here"
  ```

  ```typescript Node.js theme={null}
  const res = await fetch(
    "https://api.orbit.devotel.io/api/v1/inbox/scheduled-replies/msg_0c9e2a7b1f4d3e5a",
    {
      method: "DELETE",
      headers: { "X-API-Key": process.env.ORBIT_API_KEY },
    },
  );
  console.log(await res.json());
  ```
</CodeGroup>

Cancel flips the reply to `cancelled`, but **only while it's still parked**. Once the dispatcher has promoted the row (queued, sending, sent, failed, or already cancelled), the endpoint returns **404** — by design, there's no difference between "no such reply" and "too late to cancel" in the response you get, so a caller can't probe for the moment a reply dispatches. There is no credit to refund at cancel time: balance is debited on the send path after the dispatcher promotes the row, so a still-parked reply has no charge posted against it.

## 3. Send later vs a macro's scheduleFollowUp step

A macro can also schedule a follow-up — its `scheduleFollowUp` step rides the same scheduled-outbound pipeline. The difference is scope:

* **Send later** is a standalone, single-shot schedule. One reply, one timestamp, no macro definition to author. This is the composer's "Send at 9 AM tomorrow" case.
* **A macro's scheduleFollowUp step** is one step inside a multi-step run — it can sit alongside steps that change the conversation status, add tags, or create a task, and the whole chain runs as one run-log entry.

Reach for the endpoint on this page when the only thing you need to delay is one reply. Reach for a macro when the delayed reply is part of a repeatable sequence — say, "close the conversation, wait two days, send the satisfaction follow-up." Both approaches schedule onto the same pipeline, so a macro-authored follow-up and a composer-authored reply park, dispatch, and cancel identically.

## 4. Validation rules, front-loaded

The schedule endpoint validates before anything is stored, so an invalid timestamp comes back as a **422** the composer can render inline, not a 500 after persistence. Three checks:

1. **`scheduled_at` must be an ISO-8601 timestamp in the future.** A past or malformed value rejects with code `INVALID_SCHEDULED_AT` and the message `scheduled_at must be in the future`.
2. **`scheduled_at` must be within the 35-day platform horizon.** Anything farther out rejects with the same code and a `must be within 35 days` message. The composer-side picker enforces the same window, so the API and the dashboard agree on what counts as far enough.
3. **The body must be 1–8,000 characters.** An empty or over-long body rejects with `INVALID_BODY` and the field issues attached.

The dashboard picker blocks its **Schedule send** button on the same two timestamp rules, so most invalid input never reaches the API. When you integrate directly, treat a 422 as a fix-the-input signal rather than a retry signal.

## 5. The no-destination failure

Scheduling needs somewhere to send. The endpoint resolves the destination from the conversation: for `email` it uses the conversation's email address; for every other channel it prefers the phone number and falls back to the email address. If the conversation has **neither** a contact phone nor a contact email, the request rejects with **422** and code `NO_DESTINATION` — so a missing destination never parks a reply that can never deliver. If you hit it in a workflow, enrich the contact record on the conversation first, then re-schedule.

## 6. Channel behavior

By default the reply goes out on **the conversation's own channel** — a WhatsApp conversation schedules a WhatsApp reply, an email conversation schedules an email reply. You can override per-request with the `channel` field (for example, schedule an SMS on an email conversation), and the list is the full Inbox channel set, not just SMS and email.

Either way, the send rides your workspace's standard sender stack: the numbers, senders, and providers you configured under Inbox settings pick up the reply at dispatch exactly as if the agent had pressed send immediately. Nothing about scheduling changes carrier or sender selection — it only changes when the send leaves.

## 7. Worked example: schedule, list, watch, cancel

Two snippets — SMS and email.

### (a) SMS — schedule a morning follow-up

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/conversations/conv_4f2c91ab/scheduled-replies" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Checking in — did the return label arrive? Reply here and I will sort it.",
    "scheduled_at": "2026-09-27T09:00:00Z",
    "channel": "sms"
  }'
```

Response: **201** with `status: "scheduled"` and the reply's `id`. Keep the id — it's what cancel and the list entry reference.

### (b) Email — schedule a subject/cc-recipient summary for after hours

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/inbox/conversations/conv_91ab40fc/scheduled-replies" \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Following up on today's thread — summary of the agreed next steps is attached in the body above.",
    "scheduled_at": "2026-09-26T18:30:00Z",
    "channel": "email"
  }'
```

Response: **201**, parked until 18:30 UTC.

### List what's still pending on the conversation

```bash theme={null}
curl -X GET "https://api.orbit.devotel.io/api/v1/inbox/conversations/conv_4f2c91ab/scheduled-replies" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

While the list contains the row, the draft still exists as `status: scheduled`. Poll it (the dashboard does this on a 30-second cadence).

### Watch it promote

The dispatcher fires about once a minute. Once the scheduled time passes, the row dispatches, and it disappears from the pending list. In the dashboard the composer drawer updates on its next poll; over the API the next GET simply returns without that entry.

### Cancel before dispatch

```bash theme={null}
curl -X DELETE "https://api.orbit.devotel.io/api/v1/inbox/scheduled-replies/msg_0c9e2a7b1f4d3e5a" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

Response: **200** with `status: "cancelled"`. If you instead get **404**, the row already dispatched (or was already cancelled) — there is no window where a cancel races a dispatch silently; the 404 tells you the moment has passed.

## 8. When to NOT use send later

Send later is deliberately narrow. Three cases it is the wrong tool for:

* **A per-conversation cadence.** If every conversation needs "follow up 48 hours after close", encode it once as a macro with a `scheduleFollowUp` step, or a ticket-automation rule — not as an agent remembering to schedule each reply by hand.
* **Bulk or drip delivery.** The same message to many contacts, or a multi-touch sequence over days, belongs in **Campaigns**, where audiences, throttling, and per-contact consent tracking live. Send later addresses one reply on one conversation.
* **Workflow gating.** If the reply should wait for a human decision rather than a clock — a supervisor's approval — use the reply-approval queue, which gates content. Send later gates only timing.

## See also

* [Inbox API reference](/api-reference/endpoints/inbox) — the endpoint reference for the three send-later routes, including headers and idempotency behavior.
* [Macros — canned responses and multi-step runs](/guides/macros-canned-responses) — where the scheduleFollowUp step lives, and how a scheduling step composes with status changes and tagging.
* [Pending replies — supervisor approval queue](/guides/inbox-pending-replies) — the human-gated variant: hold a reply for sign-off rather than for a timestamp.
* [Inbox setup](/guides/inbox-setup) — channels, senders, and routing, which a scheduled reply inherits at dispatch.
* [Scheduled sending model](/concepts/scheduled-sending-model) — how the scheduled-outbound pipeline works when a reply parks and promotes.
