# Scheduled messages

Source: https://joinsimplesms.com/docs/scheduled
Index: https://joinsimplesms.com/llms.txt

To send the same message on a repeating schedule (every morning at 8:00, every
Monday, the 1st of each month), use a [recurring schedule](/docs/schedules)
instead of queueing one message per day.

Pass `scheduled_at` (ISO timestamp or epoch ms, up to 30 days out) to
`POST /v1/messages` and the message is queued instead of sent:

```bash
curl -X POST https://api.joinsimplesms.com/v1/messages   -H "Authorization: Bearer $SIMPLESMS_API_KEY"   -H "Content-Type: application/json"   -d '{"to":"+14155550132","from":"+15005550110","body":"Reminder!","scheduled_at":"2026-09-01T15:00:00Z"}'
```

The response is a `scheduled_message` with a `job_` id. Delivery happens on
the next queue flush after the timestamp (within a minute).

```json
{
  "id": "job_1788274800000aB3dE5fG7h",
  "object": "scheduled_message",
  "to": "+14155550132",
  "from": "+15005550110",
  "body": "Reminder!",
  "scheduled_at": "2026-09-01T15:00:00.000Z",
  "run_at": "2026-09-01T15:00:00.000Z",
  "status": "queued",
  "batch_id": null
}
```

`scheduled_at` is when it will send, under the same name as the request
field. `run_at` is the same value under its older name: it is a deprecated
alias, kept so existing code keeps working. Read `scheduled_at`.

## The schedule reserves nothing

Opt-out and quota are re-checked at send time, not at scheduling. A recipient
who opts out between scheduling and sending is skipped; a schedule does not
hold quota.

## Listing and canceling

`GET /v1/scheduled_messages` lists pending messages;
`GET /v1/scheduled_messages/job_...` returns one (404 once it has sent or
been canceled: from then on it is a message, under `GET /v1/messages`);
`DELETE /v1/scheduled_messages/job_...` cancels one that has not sent yet.
Cancellation races are settled atomically; a job mid-send cannot be
canceled. To send one message to many people later, schedule a
[batch](/docs/broadcasts) instead.
