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.
scheduled_at 2026-10-08T16:00:00.000Z
- scheduledSep 26 12:02Wed 7 Oct, 18:00 Berlin
- scheduledSep 29 07:40Moved: the lesson is Thursday now
- queuedOct 8 16:00Composed at the instant
- sentOct 8 16:00email.sent webhook
- 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.
TimezonesRescheduling
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- Mon5
- Tue6
- Wed7
- Thu8
- Fri9
- Sat10
- Sun11
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 timeThe 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 hoursIn 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.
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 })await aiinbx.emails.reschedule(lesson.reminderId, { scheduled_at: "2026-10-08T18:00:00+02:00",})const canceled = await aiinbx.emails.cancel(lesson.reminderId)canceled.status // "canceled"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.
Keep reading
More on building with email
- Send email APISend email from your domain or your users' own Gmail and Outlook with one API call. Opt-outs and pacing are checked first, and replies land on the thread.ThreadingMailboxesPacingSuppressions
- Email open trackingTrack email opens and clicks per domain or per send. Apple privacy prefetches and security scanner clicks arrive flagged as bots, each with its reason.Threading
- SPF DKIM DMARC checkCheck a customer's SPF, DKIM and DMARC records over an API. Live lookups at their own nameservers name the record that is wrong and the change that fixes it.Wildcard domains
- ThreadingOne conversation, however it comes back.
- Sending hoursNever email at 3am, wherever they are.
- SuppressionsUnsubscribes honoured on every send.