Scheduled sends

Schedule an email up to 90 days ahead with scheduled_at, then reschedule or cancel it before it dispatches.

Any send can carry a scheduled_at timestamp. The API accepts it immediately with a 202, holds the message, and dispatches it at the scheduled time.

Schedule a send

curl -X POST https://api.mailfully.com/v1/emails \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "orders@mail.acme.com",
    "to": "you@yourcompany.com",
    "subject": "Your order shipped",
    "text": "Order 4212 shipped.",
    "scheduled_at": "2026-07-10T09:00:00Z"
  }'

The response is the standard acceptance:

{ "id": "01JZFYK2NAB3C5D7E9F1G3H5J7" }

Rules for scheduled_at:

  • Format: an ISO 8601 datetime string, e.g. 2026-07-10T09:00:00Z.
  • Maximum horizon: 90 days ahead. Beyond that, the send is rejected with 422 validation_error (param: "scheduled_at"): scheduled_at cannot be more than 90 days in the future.
  • No minimum: a scheduled_at in the past or at the current instant is accepted and dispatched immediately. It is never an error.

Templates render at accept, not at dispatch: a scheduled send referencing a template carries the content rendered when the 202 was returned, and later template edits don't touch it. See Templates.

How scheduled sends appear

No separate scheduled status exists. A scheduled send is a queued message with a future scheduled_at, so it shows up in GET /v1/emails under status=queued:

{
  "id": "01JZFYK2NAB3C5D7E9F1G3H5J7",
  "to": ["you@yourcompany.com"],
  "from": "orders@mail.acme.com",
  "subject": "Your order shipped",
  "status": "queued",
  "last_event": "queued",
  "recipient_domain": "yourcompany.com",
  "tags": [],
  "created_at": "2026-07-08T14:02:11.000Z",
  "scheduled_at": "2026-07-10T09:00:00.000Z"
}

The detail response from GET /v1/emails/{id} has no status field; read last_event instead, which stays queued until dispatch, alongside the scheduled_at timestamp.

Reschedule

PATCH /v1/emails/{id} moves a scheduled send to a new time.

curl -X PATCH https://api.mailfully.com/v1/emails/01JZFYK2NAB3C5D7E9F1G3H5J7 \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "scheduled_at": "2026-07-12T09:00:00Z" }'

A 200 returns the full email detail with the new scheduled_at.

Constraints:

  • Only sends scheduled more than 15 minutes ahead can be moved. A send scheduled 15 minutes out or less (or sent immediately) is already committed to the delivery queue with a fixed delay; a farther-out send sits on a scheduler and can be moved. Attempting to reschedule a near-term send returns:
{
  "error": {
    "type": "validation_error",
    "message": "This send cannot be rescheduled; only sends scheduled more than 15 minutes ahead can be moved.",
    "param": "scheduled_at"
  }
}
  • The new time must be strictly in the future and at most 90 days out. This is stricter than the original send, where a past scheduled_at means "send now"; on reschedule a past or current time is a 422 validation_error (param: "scheduled_at").
  • Only queued sends move. A canceled send returns 409 conflict (A canceled email cannot be rescheduled.); any other non-queued status returns 409 conflict (Only a queued email can be rescheduled.).

Cancel

POST /v1/emails/{id}/cancel stops a send that hasn't dispatched yet. It works for any queued message, scheduled or not — unlike reschedule, it has no 15-minute restriction.

curl -X POST https://api.mailfully.com/v1/emails/01JZFYK2NAB3C5D7E9F1G3H5J7/cancel \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx"

A 200 returns the email detail, now canceled:

{
  "object": "email",
  "id": "01JZFYK2NAB3C5D7E9F1G3H5J7",
  "to": ["you@yourcompany.com"],
  "from": "orders@mail.acme.com",
  "created_at": "2026-07-08T14:02:11.000Z",
  "subject": "Your order shipped",
  "html": null,
  "text": "Order 4212 shipped.",
  "cc": [],
  "bcc": [],
  "reply_to": [],
  "last_event": "canceled",
  "scheduled_at": "2026-07-10T09:00:00.000Z",
  "tags": []
}

Cancel semantics:

  • Idempotent. Canceling an already-canceled send returns 200 again with the canceled email. Retrying is safe.
  • Too late once sending starts. If the message has left queued, cancel returns 409 conflict: Only a queued email can be canceled. If the delivery worker claims it during your request, the 409 conflict message is The email is already being sent and can no longer be canceled.
  • Canceled sends are never billed. A cancel that lands before dispatch means no delivery attempt and no metering.