> ## Documentation Index
> Fetch the complete documentation index at: https://gowa-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Scheduled Sends

> Send any message later, once or on a daily, weekly or monthly repeat, and manage schedules through /send/schedules.

![GOWA Message Scheduler Overview](https://mintcdn.com/gowa-docs/G0gUpNl3gUGyehVX/images/GOWA-Message-Scheduler-Overview.png?fit=max\&auto=format\&n=G0gUpNl3gUGyehVX\&q=85\&s=663bfdbb9e1c0bb451c0fa782380b139)

Any send request can be scheduled instead of sent immediately. Add `scheduled_at` and `timezone`; GOWA stores the request and a background worker sends it at that time. Schedules survive restarts and wait for an offline device.

Schedulable message types: text, image, file, video, audio, sticker, contact, link, location, poll and forward (`/message/{id}/forward`). Presence, typing indicators, and the experimental `/send/interactive` endpoint are not schedulable.

## Schedule fields

| Field | Required | Description |
| - | - | - |
| `scheduled_at` | yes | RFC 3339 time of the first send, in the future, e.g. `2026-11-01T09:00:00+07:00`. The first send always happens at this time. |
| `timezone` | yes | IANA zone (e.g. `Asia/Jakarta`) used to compute repeats, so "09:00 every day" stays 09:00 across DST changes. |
| `recurrence` | no | `once` (default), `daily`, `weekly` or `monthly`. |
| `weekdays` | weekly | Days to send, `0` = Sunday … `6` = Saturday, e.g. `[1, 3, 5]`. |
| `day_of_month` | monthly | `1`–`31`; clamped to the last day of shorter months. |
| `end_at` | no | Recurring only. No sends after this time. |
| `occurrence_limit` | no | Recurring only. Stop after this many sends. |

Without `scheduled_at`, the request is sent immediately as usual.

## Examples

```bash theme={null}
# once, tomorrow morning
curl -u admin:changeme -X POST http://localhost:3000/send/message \
  -H "Content-Type: application/json" -H "X-Device-Id: my-device" \
  -d '{
    "phone": "628123456789",
    "message": "Reminder: meeting at 10:00",
    "scheduled_at": "2026-11-01T09:00:00+07:00",
    "timezone": "Asia/Jakarta"
  }'
```

```json theme={null}
{
  "code": "SUCCESS",
  "message": "Success",
  "results": {
    "schedule_id": "0b5c3a8e-7f5d-4d6f-9d1e-2f8c1a7b9e10",
    "scheduled_at": "2026-11-01T02:00:00Z",
    "next_run_at": "2026-11-01T02:00:00Z"
  }
}
```

Scheduled responses have `schedule_id` instead of `message_id`.

```bash theme={null}
# every Monday, Wednesday and Friday at 08:30, 12 times
curl -u admin:changeme -X POST http://localhost:3000/send/message \
  -H "Content-Type: application/json" -H "X-Device-Id: my-device" \
  -d '{
    "phone": "120363025246125486@g.us",
    "message": "Standup in 30 minutes",
    "scheduled_at": "2026-11-02T08:30:00+07:00",
    "timezone": "Asia/Jakarta",
    "recurrence": "weekly",
    "weekdays": [1, 3, 5],
    "occurrence_limit": 12
  }'
```

```bash theme={null}
# monthly invoice on the 1st, with an uploaded file (multipart: repeat weekdays as separate fields if used)
curl -u admin:changeme -X POST http://localhost:3000/send/file \
  -H "X-Device-Id: my-device" \
  -F phone=628123456789 \
  -F file=@invoice.pdf \
  -F scheduled_at=2026-12-01T09:00:00+07:00 \
  -F timezone=Asia/Jakarta \
  -F recurrence=monthly \
  -F day_of_month=1 \
  -F end_at=2027-12-31T23:59:59+07:00
```

Uploaded files are kept on the server until the schedule finishes. In multipart bodies send `weekdays` as repeated fields (`-F weekdays=1 -F weekdays=3`).

## Manage schedules

Schedules belong to a device; send the same `X-Device-Id`.

| Action | Request |
| - | - |
| List | `GET /send/schedules` with optional `status`, `search` (recipient or text), `message_type`, `limit`, `offset` |
| Get one | `GET /send/schedules/{schedule_id}` |
| Pause | `POST /send/schedules/{schedule_id}/pause` |
| Resume | `POST /send/schedules/{schedule_id}/resume` |
| Cancel | `POST /send/schedules/{schedule_id}/cancel` |

```bash theme={null}
curl -u admin:changeme -H "X-Device-Id: my-device" \
  "http://localhost:3000/send/schedules?status=active&limit=25"
```

The list returns `results.data` and `results.pagination` (`limit`, default 25; `offset`; `total`).

### Statuses

| Status | Meaning |
| - | - |
| `active` | Waiting for its next run. |
| `running` | Claimed by the worker and sending. |
| `paused` | Held until resumed. |
| `completed` | No runs left. |
| `failed` | Gave up after retries, or the stored request was invalid. |
| `cancelled` | Cancelled by you. |

## Delivery behavior

* The worker checks for due schedules every 5 seconds.
* **Device offline or not logged in:** the run waits and retries every 30 seconds. Waiting does not count as a failed attempt, so a schedule survives long outages.
* **Send errors:** retried with backoff starting at 15 seconds and capped at 5 minutes, up to 10 attempts per occurrence. A recurring schedule then skips to its next occurrence; a one-off schedule becomes `failed`.
* **Invalid request** (for example a bad recipient): fails immediately without retrying.
* **Late runs:** an occurrence delayed past `end_at` is not sent.
* **Crashes and restarts:** shutdown does not abort a send already in flight. Before the first attempt, GOWA persists a WhatsApp message ID for the occurrence. After its 2-minute claim expires, an interrupted occurrence is requeued with that same ID and counts toward the attempt budget. Polls also retain their encryption secret across retries. This preserves message identity after an ambiguous acknowledgment, but does not guarantee exactly-once delivery.
* **Older interrupted attempts:** legacy attempts whose outcome is unknown retain conservative recovery. The worker skips that occurrence for a recurring schedule or fails a one-off schedule rather than assigning it a new message identity.

## Over MCP

The MCP tool `whatsapp_send` accepts the same schedule fields, and `whatsapp_schedule` lists, gets, pauses, resumes and cancels schedules. See [MCP server](https://gowa-docs.mintlify.site/integrations/mcp.md).

> [!NOTE]
>
> Check `GET /app/info`: `results.scheduled_sends` is `true` when the server accepts schedule fields and exposes `/send/schedules`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.