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 scheduled-email allowance for the billing period; 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.
Exemple de code
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": ["delivered@messagebird.dev"],
    "subject": "Your weekly digest",
    "html": "<p>Here is what happened this week...</p>",
    "category": "marketing",
    "scheduled_at": "2026-07-30T09:00:00Z"
  }'
The call returns 202 Accepted with the em_-prefixed message ID and status: accepted immediately, the same response shape as an immediate send: acceptance is synchronous, delivery is deferred. Omit scheduled_at and the message goes out right away.
On read endpoints the message shows status: scheduled with its scheduled_at until the send time arrives:
Exemple de code
{
  "id": "em_01ky7q24hafjgvzfg02v3m177p",
  "status": "scheduled",
  "scheduled_at": "2026-07-30T09:00:00Z",
  "category": "marketing"
}
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.
One content restriction applies: a scheduled send must carry inline content. Combining scheduled_at with a template reference is rejected with a 422 until scheduling gains template support, and scheduled_at on a batch item is rejected the same way; schedule through the single-send endpoint.

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-30T09:00:00Z) or an explicit offset (2026-07-30T09: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

Filter the message list by status to see scheduled messages that have not fired yet:
Exemple de code
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/{message_id}/cancel:
Exemple de code
curl -X POST https://us1.platform.bird.com/v1/email/messages/em_01ky7q24hafjgvzfg02v3m177p/cancel \
  -H "Authorization: Bearer bk_us1_..."
A successful cancel returns 204 No Content with 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:
    Exemple de code
    {
      "error": {
        "type": "conflict_error",
        "code": "E10005",
        "name": "EmailNotCancelable",
        "message": "This message cannot be canceled. Only scheduled messages that have not started sending can be canceled."
      }
    }
    As the send time arrives, a cancel can also lose the race to the send itself and come back 409: the message was already on its way.
  • A large send takes a few seconds to become cancelable. A scheduled send with attachments or a large body is still being stored for a few seconds after the 202, and a cancel in that window returns 409 while the message stays scheduled. If you cancel immediately after scheduling and get a 409, read the message back: if it still shows status: scheduled, retry the cancel.
  • Canceling does not refund the scheduled-email allowance. The unit you consumed at schedule time stays consumed, which stops a schedule-then-cancel loop from working around the allowance. Your regular send allowance is untouched, because it is only charged when a message actually sends.
  • Cancel is safe to retry with an Idempotency-Key like any other write.
No reschedule endpoint exists. 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.
  • Your send allowance is charged at send time. The regular send quota is consumed when the message fires, not when you schedule it, and a quota exhausted at fire time rejects the recipients rather than 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 exactly as on an immediate send.

Limits and constraints

ConstraintDetail
Send timeRFC 3339 instant, between 30 seconds and 30 days in the future
AllowanceEach plan includes a scheduled-email allowance per billing period (exceeding it returns a 422), consumed at schedule time and not refunded on cancel; separate from your regular send allowance
ContentInline html/text only; template sends and batch items cannot be scheduled yet
AttachmentsSupported, exactly like an immediate send
Content sizeThe same per-message size budget as an immediate send. Bodies and attachments are stored when you schedule and restored when the send fires
EnvelopeA send whose recipient list plus metadata and tags is very large can be rejected with a 422 asking you to reduce it or send immediately
RescheduleNo reschedule endpoint; cancel and resend with a new time

Errors

StatusCodeWhen
422E10003You have used up your plan's scheduled-email allowance for the billing period
422scheduled_at is under 30 seconds or over 30 days away
422scheduled_at combined with template, or set on a batch item
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.

Next steps