Scheduled Sends

Defer a send to a future timestamp via scheduled_at.

Request

POST /api/v1/emails
{
  "from": "hi@yourdomain.com",
  "to": ["user@example.com"],
  "subject": "Reminder",
  "text": "Your trial ends tomorrow.",
  "scheduled_at": "2026-05-01T12:00:00Z"
}

scheduled_at must be ISO 8601 UTC. If the time is ≤ 1 second from now, the send is treated as immediate.

Behavior

  1. Message stored with status: "scheduled" and scheduled_at populated.
  2. Provider is not called at request time.
  3. Vercel Cron hits /api/cron/send-scheduled every minute:
    • Selects up to 100 messages where status = 'scheduled' AND scheduled_at <= now().
    • Calls SES/SNS for each.
    • Updates status to sent (or failed) and fires message.sent webhook.
  4. Test-mode keys skip the provider call; scheduled behavior is the same.

Canceling a scheduled send

Not yet exposed — requires an internal endpoint to flip status away from scheduled before the cron picks it up. Planned.

Operational notes

  • Cron cadence is once per minute. Expect up to 60s of jitter beyond scheduled_at.
  • Cron authenticates via CRON_SECRET (optional — if unset, endpoint is open).
  • Usage is incremented when the cron sends the message, not when it's scheduled.