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

# Scheduled messages

> Queue mail to go out later, see what is queued, and cancel it before it sends.

A scheduled message is stored until its time comes, then sent by a worker that
runs every five minutes. That storage is what makes it cancellable — and what
makes it visible in the dashboard beside messages a person scheduled there.

<Note>
  **One queue, two ways in.** A key with Scheduled → Read sees everything queued
  under your organization, including messages someone scheduled in the web app.
  Each row's `scheduled_by` says which.
</Note>

<Warning>
  **Private mailboxes cannot schedule.** Queuing means storing the message body
  until the clock comes round, and a Private mailbox's guarantee is that the
  plaintext never rests anywhere Ruber can read. Encrypting it at schedule time
  would only move the problem — the worker would then need the key to send it.

  `from` must be a Smart mailbox. This is a permanent design decision, not a
  missing feature.
</Warning>

## List scheduled messages

```
GET https://app.ruber.me/api/v1/scheduled
```

**Required permission:** Scheduled → Read (`scheduled:read`)

Takes no parameters. Returns everything queued, sending, or failed for your
organization, soonest first, up to 200.

```bash theme={null}
curl https://app.ruber.me/api/v1/scheduled \
  -H "Authorization: Bearer rk_your_key_here"
```

```json theme={null}
{
  "data": [
    {
      "id": "9f2c1e40-9d7a-4b3e-8c11-0a2f6b7d9e10",
      "from": "you@yourdomain.com",
      "to": ["customer@example.com"],
      "cc": [],
      "bcc": [],
      "subject": "Following up",
      "send_at": "2026-09-09T09:00:00.000Z",
      "status": "pending",
      "last_error": null,
      "attachment_count": 0,
      "scheduled_by": "api_key",
      "created_at": "2026-09-07T14:02:11.000Z"
    }
  ]
}
```

| `status`  | Meaning                                               |
| --------- | ----------------------------------------------------- |
| `pending` | Waiting for its time. Cancellable.                    |
| `sending` | The worker has claimed it. **No longer cancellable.** |
| `failed`  | Gave up after three attempts. `last_error` says why.  |

Delivered messages leave this list — look in the Sent folder with
[`GET /v1/messages?folder=sent`](/api/messages#list-messages).

## Schedule a message

```
POST https://app.ruber.me/api/v1/scheduled
```

**Required permission:** Scheduled → Write (`scheduled:write`)

The same body as [sending](/api/sending), plus `send_at`.

```bash theme={null}
curl -X POST https://app.ruber.me/api/v1/scheduled \
  -H "Authorization: Bearer rk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "you@yourdomain.com",
    "to": ["customer@example.com"],
    "subject": "Following up",
    "text": "Just checking in on the quote.",
    "send_at": "2026-09-09T09:00:00Z"
  }'
```

| Field                       | Type            |                                                 |
| --------------------------- | --------------- | ----------------------------------------------- |
| `send_at`                   | string          | **required.** RFC 3339. Now to one month ahead. |
| `from`                      | string          | **required.** A Smart mailbox you own.          |
| `to`                        | array or string | **required.** Up to 25 recipients.              |
| `cc`, `bcc`                 | array or string | Up to 25 each.                                  |
| `subject`, `text`, `html`   | string          | As for sending.                                 |
| `in_reply_to`, `references` | string, array   | Threading.                                      |
| `attachments`               | array           | Up to 10, 8 MB total. Stored until the send.    |

```json theme={null}
{
  "id": "9f2c1e40-9d7a-4b3e-8c11-0a2f6b7d9e10",
  "send_at": "2026-09-09T09:00:00.000Z",
  "status": "pending"
}
```

`201 Created`. Keep the `id` — it is the only way to cancel.

<Note>
  **Sending happens within about five minutes of `send_at`, not to the second.**
  The worker runs on a five-minute cycle. Nobody schedules mail for 09:00 meaning
  09:00:00, and running every minute would be twelve times the work to move the
  worst case from five minutes late to one.

  Your plan's daily allowance is claimed **when the message sends**, not when you
  schedule it — so a message queued today against tomorrow's quota is charged
  tomorrow. A scheduled message can therefore fail on allowance if the day it
  lands on is already full.
</Note>

### The one-month window

`send_at` must be between now and one month ahead. Anything earlier is rejected
as `That time has already passed`, anything later as `Messages can be scheduled
up to a month ahead`. There is a minute of slack on the floor so a request that
takes a moment to arrive is not refused for it.

## Cancel a scheduled message

```
DELETE https://app.ruber.me/api/v1/scheduled/{id}
```

**Required permission:** Scheduled → Write (`scheduled:write`)

```bash theme={null}
curl -X DELETE https://app.ruber.me/api/v1/scheduled/9f2c1e40-9d7a-4b3e-8c11-0a2f6b7d9e10 \
  -H "Authorization: Bearer rk_your_key_here"
```

```json theme={null}
{ "cancelled": true, "id": "9f2c1e40-9d7a-4b3e-8c11-0a2f6b7d9e10" }
```

Only a `pending` message can be cancelled. Once the worker has claimed it the
status is `sending` and the message may already be in the mail server's hands —
there is no un-sending it, so the call is refused rather than reporting a
success you would act on.

<Warning>
  **Cancelling through the API deletes the message.** The body and any attachments
  are removed from storage.

  This differs from cancelling in the web app, which returns the message to
  Drafts — someone who cancels at nine in the morning meant "not yet", and there
  is a Drafts folder in front of them to put it in. An API caller has no drafts
  view, so cancel means cancel. Keep your own copy if you need one.
</Warning>

## Errors

| Code              | HTTP  | Cause                                                                                                                                                     |
| ----------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_request` | `400` | Malformed JSON, a bad address, a `send_at` outside the window, attachments over 8 MB, `from` is a Private mailbox, or the message is no longer `pending`. |
| `unauthorized`    | `401` | Missing, malformed, unknown or revoked key.                                                                                                               |
| `forbidden`       | `403` | The key lacks `scheduled:read` or `scheduled:write`.                                                                                                      |
| `not_found`       | `404` | No scheduled message with that id — or it is not yours.                                                                                                   |
| `rate_limited`    | `429` | Over 600 requests in a minute.                                                                                                                            |
| `internal_error`  | `500` | Storage was unreachable. Nothing was queued.                                                                                                              |

See [Errors](/api/introduction#errors) for the full response shape.
