# Scheduled sending

Scheduled sending lets you hand a message to Bird now and have it delivered at a future time you pick, instead of holding it in your own scheduler until the moment to call the API. You set one field on the send, Bird stores the message, and at the scheduled time it releases the message into the normal [delivery pipeline](/docs/guides/email/events) exactly as if you had called the API right then.

Scheduled sends count against your plan's monthly scheduled-email allowance; scheduling past it is rejected with a `422` (`E10003`).

## Scheduling a send

Add a `scheduled_at` timestamp to a normal [`POST /v1/email/messages`](/docs/api/reference/create-email-message) send. Everything else about the payload is unchanged.

```bash
curl -X POST https://us1.platform.bird.com/v1/email/messages \
  -H "Authorization: Bearer bk_us1_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "news@yourdomain.com",
    "to": ["subscriber@messagebird.dev"],
    "subject": "Your weekly digest",
    "html": "<p>Here is what happened this week...</p>",
    "category": "marketing",
    "scheduled_at": "2026-07-01T09:00:00Z"
  }'
```

The call returns `202 Accepted` with the `em_`-prefixed message ID immediately, the same as an [immediate send](/docs/guides/email/sending-email) — acceptance is synchronous, delivery is deferred. Omit `scheduled_at` (or send `null`) and the message goes out right away.

On [read endpoints](/docs/api/reference/get-email-message) the message shows `status: scheduled` until its send time arrives. When the time comes, Bird releases it into the pipeline and its status advances through the usual states (`accepted` → `processed` → `delivered`, and so on). `scheduled_at` stays set on the message after it fires, so you can always see when a message was scheduled for.

## Choosing the send time

`scheduled_at` is an absolute [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339) timestamp. Two rules govern it:

- **It must be between 30 seconds and 30 days in the future.** Earlier than 30 seconds from now, or further out than 30 days, is rejected with a `422`. The 30-second floor keeps a "schedule" from racing an immediate send; the 30-day ceiling is the maximum horizon.
- **It is an exact instant, not a wall-clock time in some timezone.** Include a UTC `Z` (`2026-07-01T09:00:00Z`) or an explicit offset (`2026-07-01T09:00:00-04:00`, which is the same instant as `13:00:00Z`). Bird compares the instant against the current time; it does not interpret a bare local time or apply a recipient's timezone. To send at "9am in each recipient's local time," compute that instant yourself and schedule one send per timezone.

Relative expressions like `"in 2 hours"` are **not** accepted — send a resolved timestamp.

## Viewing scheduled messages

Scheduled messages that have not fired yet are served from a dedicated store, so filter the [message list](/docs/api/reference/list-email-messages) by status to see them:

```bash
curl "https://us1.platform.bird.com/v1/email/messages?status=scheduled" \
  -H "Authorization: Bearer bk_us1_..."
```

`status=canceled` lists messages you canceled before they sent. Once a scheduled message fires it moves into the pipeline and shows up under the delivery statuses (`accepted`, `delivered`, `bounced`, and so on), same as any other send. In the [dashboard](/docs/knowledge-base/getting-started/dashboard-tour), the email log offers the same `scheduled` and `canceled` filters.

## Canceling a scheduled send

Cancel a message any time before it starts sending with [`POST /v1/email/messages/{id}/cancel`](/docs/api/reference/cancel-email-message):

```bash
curl -X POST https://us1.platform.bird.com/v1/email/messages/em_019c1930687b7bfa.../cancel \
  -H "Authorization: Bearer bk_us1_..."
```

A successful cancel returns `204 No Content` — there is no response body. The message's status becomes `canceled`, it never sends, and an `email.canceled` webhook fires. A few things to know:

- **Only a still-scheduled message can be canceled.** If the message has already started sending, already sent, or was already canceled, the call returns `409` (`E10005`). There is a brief window as the send time arrives where a cancel can lose the race to the send itself and come back `409` — the message was already on its way.
- **Canceling does not refund the scheduled-email allowance.** The unit you consumed at schedule time stays consumed. This is deliberate: it stops a schedule-then-cancel loop from working around your allowance. Your regular send allowance is untouched, because it is only charged when a message actually sends (see [Limits](#limits-and-constraints)).
- **Cancel is idempotent-safe to retry** with an [`Idempotency-Key`](/docs/guides/idempotency) like any other write.

There is no reschedule endpoint. To move a scheduled send to a different time, cancel it and submit a new send with the new `scheduled_at` — you get a fresh `em_` ID.

## What stays the same, and what happens at send time

Scheduling changes _when_ a message is released, not how it is built or governed.

- **Field and domain validation are up front.** Payload validation and the sender-domain check run when you call the API, so a malformed scheduled send fails immediately with a `422` — you find out now, not at 9am.
- **The sender domain is re-checked at send time.** If your `from` domain is no longer verified when the scheduled time arrives, the message is not sent: its recipients come back `rejected` with a reason, rather than going out from an unverified domain. Keep the domain verified for the whole window between scheduling and sending.
- **Suppression is evaluated at send time**, against your [suppression list](/docs/guides/email/suppressions) as it stands then — so a recipient who unsubscribes between scheduling and sending is still honored.
- **[Category](/docs/guides/email/categories), [tags, and metadata](/docs/guides/email/sending-email#tags-vs-metadata) are unchanged**, and are echoed on the scheduled send's webhook events just like an immediate send.

## Limits and constraints

| Constraint   | Detail                                                                                                                                                                                     |
| :----------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Send time    | RFC 3339 instant, between 30 seconds and 30 days in the future                                                                                                                             |
| Allowance    | Each plan includes a monthly scheduled-email allowance (exceeding it returns a `422`), consumed at schedule time and **not** refunded on cancel; separate from your regular send allowance |
| Attachments  | Supported. A scheduled send may carry `attachments`, exactly like an immediate send                                                                                                        |
| Content size | The same per-message limits as an immediate send (up to the 20 MB total cap). Large bodies and attachments are stored when you schedule and restored when the send fires                   |
| Reschedule   | No reschedule endpoint — cancel and resend with a new time                                                                                                                                 |

## Errors

| Status | Code     | When                                                                                      |
| :----- | :------- | :---------------------------------------------------------------------------------------- |
| `422`  | `E10003` | You have used up your plan's monthly scheduled-email allowance                            |
| `422`  | —        | `scheduled_at` is under 30 seconds or over 30 days away                                   |
| `409`  | `E10005` | The message can no longer be canceled — it already started sending, sent, or was canceled |
| `404`  | —        | No message with that ID in this workspace                                                 |

## Webhooks

Two events are specific to scheduling, in addition to the usual [delivery events](/docs/guides/email/events):

- **`email.scheduled`** fires when a message is accepted with a future `scheduled_at`, and carries the `scheduled_at` time.
- **`email.canceled`** fires when a scheduled message is canceled before it sends.

When a scheduled message fires, the normal `email.accepted` → delivery chain follows unchanged.

## Scheduling a broadcast

Audience [broadcasts](/docs/guides/email/audiences) accept their own `scheduled_at`: create the broadcast with `send: true` and a future `scheduled_at`, and it dispatches to the whole audience at that time. Created without `send`, a broadcast stays an editable draft you can send later.

## Next steps

- [Sending email](/docs/guides/email/sending-email) — the full send payload and the async 202 model
- [Suppressions](/docs/guides/email/suppressions) — who Bird won't deliver to, and why, evaluated at send time
- [Events and webhooks](/docs/guides/email/events) — the delivery pipeline a scheduled message enters when it fires
- [Idempotency](/docs/guides/idempotency) — safe retries for the schedule and cancel calls
- [API reference](/docs/api/reference/create-email-message) — full request and response schemas