# POST /scheduled-communications

**Resource:** [Scheduled Communications](./scheduled-communications.md)  
**Scopes:** `dispatcher:write`, `email:send | sms:send | calls:initiate`  
**Write operation:** yes

Schedule a future email, SMS, or AI voice call. Requires dispatcher:write PLUS a channel-specific scope (email:send, sms:send, or calls:initiate). The row is created with status="pending". The scheduler tick picks it up at or after scheduled_for. If respect_business_hours=true, the scheduler holds the row outside of the configured business window and re-schedules to the next window start in the callee's timezone.

## Parameters

| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `channel` | body | string | yes | Channel: email, sms, or voice_call |
| `scheduled_for` | body | string | yes | ISO 8601 dispatch time. Must not be more than 1 minute in the past. |
| `payload` | body | object | yes | Channel-specific payload. email: {to_email, subject, html_body, [from_email, sender_user_id, mode, contact_id, deal_id, customer_id, cc, to_name]}. sms: {phone_number_id, to_number, message_body}. voice_call: {voice_agent_outbound_config_id, to_number, [dynamic_variables, metadata, follow_ups]}. The follow_ups object enables multi-attempt voice cadence: {max_attempts (integer, total including first), follow_up_delays (array of hours between each retry -- decimal allowed, e.g. 0.5 for 30 min), voice_agent_outbound_config_id (uuid), to_number (E.164), [respect_business_hours, dynamic_variables, contact_id, customer_id, deal_id]}. |
| `timezone` | body | string | no | IANA timezone for business-hours evaluation (e.g. "Australia/Sydney"). Falls back to company default if omitted. |
| `respect_business_hours` | body | boolean | no | If true, hold delivery until callee is within business hours. Default false. |
| `contact_id` | body | uuid | no | Link to a contact UUID for timeline logging |
| `customer_id` | body | uuid | no | Link to a customer/account UUID |
| `deal_id` | body | uuid | no | Link to a deal/opportunity UUID for timeline logging |
| `related_entity_type` | body | string | no | Generic entity type for additional context linkage |
| `related_entity_id` | body | uuid | no | Generic entity ID for additional context linkage |
| `cancel_policy` | body | object | no | Optional cancellation policy configuration (jsonb) |

## Request example

```bash
# Schedule an email for tomorrow morning (business hours enforced)
curl -X POST \
  "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "email",
    "scheduled_for": "2026-05-01T09:00:00Z",
    "timezone": "Australia/Sydney",
    "respect_business_hours": true,
    "payload": {
      "to_email": "client@example.com",
      "subject": "Following up on your quote",
      "html_body": "<p>Hi Sarah, just checking in...</p>",
      "contact_id": "abc123..."
    },
    "contact_id": "abc123..."
  }'

# Schedule an AI voice call for next week
curl -X POST \
  "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "voice_call",
    "scheduled_for": "2026-05-05T10:00:00Z",
    "timezone": "Australia/Brisbane",
    "respect_business_hours": true,
    "payload": {
      "voice_agent_outbound_config_id": "config-uuid...",
      "to_number": "+61412345678",
      "dynamic_variables": { "contact_name": "Sarah", "deal_name": "Website Project" }
    }
  }'

# Schedule a voice call with multi-attempt cadence (3 attempts: now, +2h, +24h)
curl -X POST \
  "https://api.trustpager.com/functions/v1/api/v1/scheduled-communications" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "voice_call",
    "scheduled_for": "2026-05-05T10:00:00Z",
    "timezone": "Australia/Brisbane",
    "respect_business_hours": true,
    "contact_id": "contact-uuid...",
    "payload": {
      "voice_agent_outbound_config_id": "config-uuid...",
      "to_number": "+61412345678",
      "dynamic_variables": { "contact_name": "Sarah" },
      "follow_ups": {
        "max_attempts": 3,
        "follow_up_delays": [2, 24],
        "voice_agent_outbound_config_id": "config-uuid...",
        "to_number": "+61412345678",
        "respect_business_hours": true,
        "contact_id": "contact-uuid..."
      }
    }
  }'
```

## Response example

```json
{
  "data": {
    "id": "60b5889e-c8e1-4273-836e-67a3f0b3231a",
    "channel": "email",
    "status": "pending",
    "scheduled_for": "2026-05-01T09:00:00+00:00",
    "timezone": "Australia/Sydney",
    "respect_business_hours": true,
    "payload": { "to_email": "client@example.com", "subject": "Following up on your quote", "html_body": "..." },
    "contact_id": "abc123...",
    "attempt_count": 0,
    "source": "api",
    "created_at": "2026-04-23T15:00:00Z"
  },
  "meta": { "credits_remaining": 49999 }
}
```

## Notes

- Scheduling email requires dispatcher:write AND email:send scopes.
- Scheduling SMS requires dispatcher:write AND sms:send scopes.
- Scheduling a voice call requires dispatcher:write AND calls:initiate scopes.
- When respect_business_hours=true, the scheduler defers rows outside Mon-Fri 9-5 in the callee's timezone (configurable per company).
- The scheduler tick runs every minute. Expect up to 1 minute of latency after scheduled_for.
- Rows retry with exponential backoff: email/SMS up to 3 attempts, voice_call up to 2.
- Voice cadence (multi-attempt): include a follow_ups object in the voice_call payload to automatically schedule retries. follow_up_delays is an array of hours between each attempt (supports decimals: 0.5 = 30 min). The cadence stops early if the call connects and the contact speaks for 10+ seconds (real conversation detected). Retries are queued as new pending rows in the dispatcher.

---
Base URL: `https://api.trustpager.com/functions/v1/api/v1` — Auth: `Authorization: Bearer YOUR_API_KEY`