# Scheduling

One-off scheduled sends, recurring daily/weekly rules with their own timezone, quiet hours that hold instead of drop, and AI Push — every schedule type Native Notify runs.

Native Notify can fire a notification at a future instant, or on a recurring rule — with the timezone always resolved to the right UTC moment for you.

## One-off scheduled sends

Schedule a full-audience notification for a date and time:

```text
POST https://app.nativenotify.com/api/schedule/notification
```

```json
{
  "appId": 123,
  "appToken": "yourAppToken",
  "title": "Service this Sunday",
  "body": "Doors open at 9:00.",
  "pushData": { "screen": "events" },
  "timezone": "America/New_York",
  "year": 2026, "month": 8, "day": 27, "hour": 9, "minute": 30,
  "utcYear": 2026, "utcMonth": 8, "utcDay": 27, "utcHour": 13, "utcMinute": 30
}
```

- Send **both** the wall-clock fields (`year`…`minute`, what the user picked) and the **UTC fields** (`utc*`) the server fires on. `timezone` is the IANA zone the schedule is displayed in.
- ⚠️ **Months are 0-based** (`8` = September) in this raw API — it matches JavaScript's `Date#getUTCMonth()`, and the every-minute scheduler matches on exactly that number. Agents never touch this: the MCP tool `schedule_notification` takes an ISO 8601 instant plus an IANA timezone and derives all fields for you.
- Response: `201` with `"Notification scheduled successfully!"`; list what is scheduled (including already-sent items and their read state) with `GET /api/scheduled/notifications/:appId` or the MCP tool `list_scheduled_notifications`.
- A scheduled send is a **mass** push in the public API today. To target a segment, use a recurring rule (below) or an indie/group send.

## Recurring rules

Recurring schedules live on the Agent Notify side and are managed from the dashboard's **Scheduling** tab, by the agent in chat, or over the session API:

```text
GET    /api/agent/apps/:appId/schedules                 — rules + quiet-hours settings
GET    /api/agent/apps/:appId/schedules/upcoming        — next fires (rules + pending one-offs)
POST   /api/agent/apps/:appId/schedules                 — create a rule
PATCH  /api/agent/apps/:appId/schedules/:ruleId         — update / pause / resume
DELETE /api/agent/apps/:appId/schedules/:ruleId         — delete a rule
GET    /api/agent/apps/:appId/schedules/:ruleId/runs    — recent fire history
```

A rule carries:

| Field                    | Notes                                                                                                 |
| ------------------------ | ----------------------------------------------------------------------------------------------------- |
| `name`                   | Rule label, e.g. "Weekly digest" (≤120 chars).                                                        |
| `title`, `body`          | The notification itself (≤200 / ≤4000 chars).                                                         |
| `enabled`                | Pause/resume without deleting.                                                                        |
| `frequency`              | `daily` or `weekly`.                                                                                  |
| `dayOfWeek`              | `0`–`6` (0 = Sunday) for weekly rules.                                                                |
| `timeOfDay`              | 24h `"HH:MM"`, e.g. `"09:00"`.                                                                        |
| `timezone`               | IANA zone the rule fires in — the rule follows **its own** wall clock, DST included.                  |
| `audienceKind`           | `all` or `segment` (+ `segmentId`).                                                                   |
| `skipIfActiveWithinDays` | Optional `1`–`365`: subscribers who opened the app within N days are skipped ("they already saw it"). |
| `pushData`               | JSON payload delivered with the notification (≤16 KiB).                                               |

The runner ticks **every minute**: an occurrence is dispatched when the tick lands on it (or within a 15-minute grace window) and never twice. Blocked sends (lapsed trial, membership gate) answer `201` with an explanation and are recorded as `blocked` runs — the rule keeps its schedule.

## Quiet hours — hold, don't drop

Quiet hours are per-app and apply to recurring rules: while enabled, an occurrence whose fire moment falls inside the window is **held**, not dropped, and sent the moment the window ends.

```text
GET /api/agent/apps/:appId/schedules/settings
PUT /api/agent/apps/:appId/schedules/settings
```

```json
{ "quietHoursEnabled": true, "quietHoursStart": "22:00", "quietHoursEnd": "07:00" }
```

Defaults are `22:00`–`07:00`; the window may cross midnight; start and end must differ (a zero-length window never holds).

## AI Push

"AI Push" is a weekly schedule where the service **writes** the notification from a description of your app and sends it on your chosen day/time:

```text
POST /api/schedule/ai/mass/push      — create/update the weekly mass schedule
POST /api/schedule/ai/indie/push     — the indie (subscriber) variant
GET  /api/scheduled/ai/mass/push     — list what is configured
```

Agents manage these with the `list_ai_pushes` / `schedule_ai_push` / `update_ai_push` / `delete_ai_push` tools. AI Push requires a plan that includes it.

## Good timing

The dashboard's scheduling view shows each rule's next fire and compares it with your app's **best send times** (weekday × hour from real open activity) so you can pick a window that gets read. The same data is available to agents through `get_best_send_times`.
