Sending
Scheduling
Schedule an email up to 30 days ahead with scheduled_at, then reschedule or cancel it while it is still scheduled.
Add scheduled_at to POST /emails to send an email later. A scheduled email is accepted now, returns an ID immediately, and is released at the time you chose.
Set a send time
scheduled_at accepts two forms.
ISO 8601. Always include a time zone, for example Z for UTC or an offset such as +02:00.
{ "scheduled_at": "2026-11-20T09:00:00Z" }Natural language in the form in N unit, where the unit is second, minute, min, hour, hr, day or week, singular or plural.
{ "scheduled_at": "in 1 hour" }Other phrases, such as tomorrow, are not understood and return a validation_error.
curl -X POST https://api.sooner.dev/emails \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "Acme <hello@mail.yourcompany.com>",
"to": ["ada@example.com"],
"subject": "Your trial ends tomorrow",
"html": "<p>Your trial ends tomorrow.</p>",
"scheduled_at": "in 2 days"
}'Rules
- The time must be at most 30 days from now. Further out returns
422 validation_error. - A time that is in the past, or within about one second from now, sends the email immediately instead of scheduling it.
- Scheduled emails have status
scheduledandlast_eventscheduled. Thescheduled_atfield on the email shows the chosen time. POST /emails/batchdoes not acceptscheduled_at.- A scheduled email counts toward your quota when you create it. If you cancel it, the count is returned.
- Checks happen again when the email is released. Recipients added to the suppression list in the meantime are dropped, and if none remain the email fails. If the sending domain is no longer verified, the email fails too.
Reschedule
PATCH /emails/{id} changes the send time of an email that is still scheduled. It needs a full_access key.
curl -X PATCH https://api.sooner.dev/emails/01jcz8w3f2x9k4m7n6p5q2r1s0 \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "scheduled_at": "2026-11-21T09:00:00Z" }'{ "object": "email", "id": "01jcz8w3f2x9k4m7n6p5q2r1s0" }The same 30-day limit applies, counted from the moment of the update.
Cancel
POST /emails/{id}/cancel cancels a scheduled email. It needs a full_access key. The status becomes canceled, the email is never sent, and its quota count is returned.
curl -X POST https://api.sooner.dev/emails/01jcz8w3f2x9k4m7n6p5q2r1s0/cancel \
-H "Authorization: Bearer $SOONER_API_KEY"{ "object": "email", "id": "01jcz8w3f2x9k4m7n6p5q2r1s0" }Only emails with status scheduled can be rescheduled or canceled. For anything else, the API returns 422 validation_error: "Only scheduled emails can be updated." or "Only scheduled emails can be canceled."
With the Node SDK
import { Resend } from "resend";
const resend = new Resend(process.env.SOONER_API_KEY, { baseUrl: "https://api.sooner.dev" });
const { data } = await resend.emails.send({
from: "Acme <hello@mail.yourcompany.com>",
to: ["ada@example.com"],
subject: "Your trial ends tomorrow",
html: "<p>Your trial ends tomorrow.</p>",
scheduledAt: "in 2 days",
});
await resend.emails.update({ id: data!.id, scheduledAt: "2026-11-21T09:00:00Z" });
await resend.emails.cancel(data!.id);Scheduled broadcasts work the same way. See Broadcasts.