Scheduled email API

Send it later, and change your mind until then

Add scheduled_at to a send and the email waits, up to 30 days. Move it or cancel it by id until its scheduled time, and it goes out under the suppressions, thread and tracking of that moment.

Two calls, three days apart.

POST /emails{  "from": "Yourapp <lessons@mail.yourapp.com>",  "to": "mia.okafor@gmail.com",  "subject": "Your lesson tomorrow at 16:00",  "text": "See you at 16:00 — reply to move it.",  "scheduled_at": "2026-10-07T18:00:00+02:00"}
PATCH /emails/eml_7hq2{  "scheduled_at": "2026-10-08T18:00:00+02:00"}

The email, read back after it left.

GET /emails/eml_7hq2delivered

scheduled_at 2026-10-08T16:00:00.000Z

  1. scheduledSep 26 12:02Wed 7 Oct, 18:00 Berlin
  2. scheduledSep 29 07:40Moved: the lesson is Thursday now
  3. queuedOct 8 16:00Composed at the instant
  4. sentOct 8 16:00email.sent webhook
  5. deliveredOct 8 16:00email.delivered webhook

Timezones

Whose nine o'clock

scheduled_at is an instant, so it carries its offset. Nine in Berlin, New York and Tokyo are three different values, and a time with no zone is refused instead of guessed.

Timezones
Berlin2026-10-05T09:00:00+02:0007:00 UTC
New York2026-10-05T09:00:00-04:0013:00 UTC
Tokyo2026-10-05T09:00:00+09:0000:00 UTC
No offset2026-10-05T09:00:00422

Rescheduling

Move it without resending it

PATCH the email with a new scheduled_at. It keeps its id, so whatever your app stored still points at it. Once it has begun sending, the call answers 409.

Rescheduling
  1. Mon5
  2. Tue6
  3. Wed7
  4. Thu8
  5. Fri9
  6. Sat10
  7. Sun11
PATCH /emails/eml_7hq2scheduledsame id, new time
PATCH, once it is sending409 not_scheduled

Cancelling

Cancelled means it stays cancelled

Cancel is terminal. The email keeps its idempotency key, so a retry of the original request returns the cancelled email instead of sending it, and it gives back its place in your allowance.

Cancelling
  • POST /emails/eml_7hq2/cancelcanceledNever sent, and its timeline says canceled
  • POST /emails, same Idempotency-KeycanceledYour retry gets the cancelled email back
  • PATCH /emails/eml_7hq2409 not_scheduledCancelled is the end of it

At send time

Written for the minute it leaves

A scheduled email is composed again when its time comes, and what goes out is that version. Suppressions, the thread it answers, the domain's tracking and your sending hours are read then, not when you called.

At send time
Depends on26 September, when it was scheduledThursday 18:00, when it leaves
RecipientsMia, with her father on ccHis address hard-bounced on Wednesday, so he is dropped and Mia still gets it
ThreadSent into the booking's thread, after the confirmationMia answered on Wednesday, and the reminder replies to her message
TrackingThe domain tracked no opensOpens were switched on since, so this one is tracked, unless the send said otherwise
HoursNo window in the wayA sending-hours rule closed at that minute holds it until the window opens

The rules, in one place

How far ahead
Up to 30 days from the moment of the call. A reschedule counts its 30 days from when you make it.
The format
An RFC 3339 timestamp with an offset or a Z. Without one it is rejected with invalid_request.
A time already past
Sends straight away, and comes back queued rather than scheduled.
What can change
The time, and nothing else. For a new subject, body or recipient, cancel and schedule a fresh one.
Where it works
emails.send, threads.reply and threads.forward all take scheduled_at, and a scheduled reply is threaded when it leaves.
Who can move it
A key with the sending or full scope. A read key can list the queue but not touch it.

A time on one email, or hours for all of them

scheduled_at belongs to one message: this reminder, not before Thursday at six. Sending hours are a rule over the mail that leaves, like weekdays nine to five in a timezone you pick, and they hold any mail they cover that arrives outside them.

The two stack. A scheduled email is the earliest a message may go, and a closed window can still make it wait. For a per-message instant, use scheduled_at; for a standing policy about when your product is allowed to email people, write an hours rule.

Sending hours

In your code

A reminder that follows the booking

Store the email id next to the thing it is about. When the booking moves, move the email; when it is called off, cancel it. There is no job runner, queue table or cron of your own.

1emails.sendthe time goes in with its offset
const email = await aiinbx.emails.send({  from: "Yourapp <lessons@mail.yourapp.com>",  to: "mia.okafor@gmail.com",  subject: "Your lesson tomorrow at 16:00",  text: "See you at 16:00 — reply to move it.",  scheduled_at: "2026-10-07T18:00:00+02:00",}) await lessons.update(lessonId, { reminderId: email.id })
2emails.reschedulethe lesson moved
await aiinbx.emails.reschedule(lesson.reminderId, {  scheduled_at: "2026-10-08T18:00:00+02:00",})
3emails.cancelthe lesson was called off
const canceled = await aiinbx.emails.cancel(lesson.reminderId)canceled.status // "canceled"
4emails.listeverything still waiting
for await (const email of aiinbx.emails.list({  status: "scheduled",})) {  console.log(email.scheduled_at, email.subject, email.to)}

Questions

How far in advance can I schedule an email?

Up to 30 days from the moment of the call; a later scheduled_at is rejected with invalid_request. Rescheduling applies the same 30 days from when you move it. For anything further out, keep the date in your own app and schedule the send once it is inside the window.

Can I cancel a scheduled email?

Yes, with POST /emails/{id}/cancel while its status is still scheduled. The email becomes canceled and is never sent, and it no longer counts against your allowance. Once it has begun sending, the call returns 409 not_scheduled.

Can I change the content of a scheduled email?

No, only its time: PATCH /emails/{id} takes a new scheduled_at and nothing else. To change the subject, body or recipients, cancel it and schedule a new one. What does change on its own is decided at send time: suppressed recipients are dropped and a reply answers the latest message on its thread.

How do I send an email at 9am in the recipient's timezone?

Work out 9am in their zone and pass it with its offset, for example 2026-10-05T09:00:00-04:00 for New York in October, as one send per recipient. A timestamp without an offset is refused. If every email to a region should wait for its working hours, a sending-hours rule does that without a time on each send.

What happens if scheduled_at is in the past?

The email is sent straight away, exactly as if the field had been left out, and the response says queued rather than scheduled.

Do I need a cron job to send emails later?

No. The API holds the email and releases it at its time, so your code makes one call and keeps the id it gets back. Nothing of yours has to be running at the moment it goes.

Which webhooks fire for a scheduled email?

The same as for any send, once it leaves: email.sent, then email.delivered, email.bounced or the others. Scheduling, moving and cancelling are answered in the response to your own call and recorded in the email's events timeline, without a webhook of their own.

Give your product an inbox. The hard parts are already handled.

Get started