Skip to main content

Guides

Scheduling

Send later with scheduled_at: a relative duration, a Date, or an ISO 8601 string.


Pass scheduled_at to send a message at a future time instead of right away.

The easiest way is a relative duration: an object of units added to the send time. Fields combine, so { days: 1, hours: 5 } is 29 hours from now:

import { mail } from 'postboi'

await mail({
	to: 'contact@example.com',
	subject: 'Your weekly digest',
	body: '<p>Here is what you missed.</p>',
	scheduled_at: { days: 1, hours: 5 }
})
import { mail } from 'postboi'

await mail({
	to: 'contact@example.com',
	subject: 'Your weekly digest',
	body: '<p>Here is what you missed.</p>',
	scheduled_at: { days: 1, hours: 5 }
})

Every unit is optional: seconds, minutes, hours, days, weeks, months, years. Months and years are calendar-aware (a real “+1 month”).

You can also pass an absolute Date, or an ISO 8601 string (include an explicit timezone offset or Z, otherwise it’s read as UTC):

await mail({
	to: 'contact@example.com',
	subject: 'Launch day',
	body: '<p>We are live.</p>',
	scheduled_at: '2026-07-10T14:30:00Z'
})
await mail({
	to: 'contact@example.com',
	subject: 'Launch day',
	body: '<p>We are live.</p>',
	scheduled_at: '2026-07-10T14:30:00Z'
})

An invalid or unparseable scheduled_at throws before anything is sent.

Provider support

Scheduling is done by the provider, so it only works where the provider offers it:

Provider How it’s sent
the Postboi provider Up to 30 days ahead; reschedule with mail.messages.reschedule(id, when) or cancel, from code or the dashboard. See the Postboi provider.
Resend Passed through as scheduled_at.
Brevo Passed through as scheduledAt.
Mailgun Sent as o:deliverytime.
SendGrid Sent as send_at.

Every other provider ignores scheduled_at and sends immediately, so don’t lean on it with a provider that doesn’t support it.

Scheduling composes with bulk sending: give each message in the array its own scheduled_at.

Cancelling a scheduled send

Keep the id mail() returned and pass it to cancel():

import { mail, cancel } from 'postboi'

const { id } = await mail({
	to: 'contact@example.com',
	subject: 'Reminder',
	body: '<p>See you tomorrow.</p>',
	scheduled_at: { days: 1 }
})

// Plans changed:
await cancel(id)
import { mail, cancel } from 'postboi'

const { id } = await mail({
	to: 'contact@example.com',
	subject: 'Reminder',
	body: '<p>See you tomorrow.</p>',
	scheduled_at: { days: 1 }
})

// Plans changed:
await cancel(id)

cancel() resolves the same provider mail() uses; on a provider instance it’s provider.cancel(id).

Provider How it cancels
the Postboi provider POST /v1/messages/:id/cancel (also available from the dashboard).
Resend POST /emails/:id/cancel.
Brevo DELETE /v3/smtp/email/:id.

Providers without a cancellation API reject with a PostboiError code cancel_not_supported, loudly, so a send you believe is cancelled never quietly goes out. (The postboi/mock provider records cancellations on .canceled for tests.)

Rescheduling

On the Postboi provider a scheduled message can also be moved instead of cancelled, any time until it sends. mail.messages.reschedule accepts the same formats as scheduled_at:

import { mail } from 'postboi'

await mail.messages.reschedule(id, { days: 2 }) // or a Date / ISO 8601 string
import { mail } from 'postboi'

await mail.messages.reschedule(id, { days: 2 }) // or a Date / ISO 8601 string