Documentation
Sign inGet started

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 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 send. Everything else about the payload is unchanged.
Code example
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 — acceptance is synchronous, delivery is deferred. Omit scheduled_at (or send null) and the message goes out right away.
On read endpoints 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 (acceptedprocesseddelivered, 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 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 by status to see them:
Code example
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, 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:
Code example
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).
  • Cancel is idempotent-safe to retry with an Idempotency-Key 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 as it stands then — so a recipient who unsubscribes between scheduling and sending is still honored.
  • Category, tags, and metadata are unchanged, and are echoed on the scheduled send's webhook events just like an immediate send.

Limits and constraints

ConstraintDetail
Send timeRFC 3339 instant, between 30 seconds and 30 days in the future
AllowanceEach 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
AttachmentsSupported. A scheduled send may carry attachments, exactly like an immediate send
Content sizeThe 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
RescheduleNo reschedule endpoint — cancel and resend with a new time

Errors

StatusCodeWhen
422E10003You have used up your plan's monthly scheduled-email allowance
422scheduled_at is under 30 seconds or over 30 days away
409E10005The message can no longer be canceled — it already started sending, sent, or was canceled
404No message with that ID in this workspace

Webhooks

Two events are specific to scheduling, in addition to the usual delivery 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 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 — the full send payload and the async 202 model
  • Suppressions — who Bird won't deliver to, and why, evaluated at send time
  • Events and webhooks — the delivery pipeline a scheduled message enters when it fires
  • Idempotency — safe retries for the schedule and cancel calls
  • API reference — full request and response schemas