# Mac-Sync Send Queue Endpoint Contracts

## Endpoints

### `GET /client/imessage/send-queue/pending`
Fetch pending messages for a device.

**Auth**: Device token via `Authorization: Bearer <token>`

**Response** (200 OK):
```json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "toHandle": "+14155552671",
        "body": "Message content",
        "createdAt": "2026-04-22T12:00:00.000Z"
      }
    ]
  }
}
```

**Implementation**:
- Queries `icloud.send_queue WHERE device_id = $1 AND status = 'queued'`
- Orders by `created_at ASC`
- Limits to 50 rows per call
- Maps snake_case DB columns to camelCase response

---

### `POST /client/imessage/send-queue/:id/result`
Report the result (sent or failed) for a queued message.

**Auth**: Device token via `Authorization: Bearer <token>`

**Path Parameters**:
- `id`: UUID of the send_queue row

**Request Body**:
```json
{
  "status": "sent" | "failed",
  "error": "optional error message"
}
```

**Response** (200 OK):
```json
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "sent"
  }
}
```

**Behavior**:
- If `status='sent'`: updates row with `status='sent'`, `sent_at=now()`
- If `status='failed'`: updates row with `status='failed'`, `failure_reason=error` (or NULL if no error)
- Returns 500 on database error or invalid parameters

---

### `POST /admin/send-queue/enqueue`
Enqueue a message to be sent (called by quinn.api outreach-dispatcher).

**Auth**: Service token via `Authorization: Bearer <token>`

**Request Body**:
```json
{
  "batchItemId": "550e8400-e29b-41d4-a716-446655440000",
  "deviceId": "550e8400-e29b-41d4-a716-446655440001",
  "toHandle": "+14155552671",
  "body": "Rendered message template with variables substituted"
}
```

**Response** (200 OK):
```json
{
  "success": true,
  "data": {
    "sendQueueId": "550e8400-e29b-41d4-a716-446655440002"
  }
}
```

**Response** (400 Bad Request):
```json
{
  "success": false,
  "error": {
    "code": "validation_error",
    "message": "invalid request payload",
    "issues": [
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "number",
        "path": ["batchItemId"],
        "message": "Expected string, received number"
      }
    ]
  }
}
```

**Behavior**:
- Validates request body with Zod
- Inserts row into `icloud.send_queue(device_id, to_handle, body, status='queued')`
- Returns the generated PostgreSQL UUID as `sendQueueId`
- Returns 400 on validation error with detailed Zod issues
- Returns 500 on database error

---

## Database Schema

```sql
CREATE TABLE icloud.send_queue (
  id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  device_id         UUID NOT NULL REFERENCES icloud.devices(id) ON DELETE CASCADE,
  to_handle         TEXT NOT NULL,
  body              TEXT NOT NULL,
  status            TEXT NOT NULL DEFAULT 'queued',
  send_queue_id     TEXT,
  sent_at           TIMESTAMPTZ,
  failure_reason    TEXT,
  created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at        TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_sendq_device_status ON icloud.send_queue(device_id, status);
CREATE INDEX idx_sendq_created ON icloud.send_queue(created_at DESC);
CREATE INDEX idx_sendq_status ON icloud.send_queue(status);
```

---

## Status Flow

```
[pending in DB] 
    ↓
GET /client/imessage/send-queue/pending
    ↓ (device fetches and processes)
POST /client/imessage/send-queue/:id/result (status='sent'|'failed')
    ↓
[row updated with sent_at or failure_reason]
    ↓ (quinn.api outreach-dispatcher polls every 30s)
[outreach_batch_items.status updated to 'sent']
```

---

## Per-module send-queue endpoints

Modules other than iMessage use the same wire format but live in
module-specific tables (`macsync.<module>_send_queue`), accessed through
the generic `createSendQueueRepo` factory at
`src/shared/sendQueue/SendQueueRepo.ts`. Every module exposes:

```
GET  /client/<module>/send-queue/pending
POST /client/<module>/send-queue/:id/result
```

with response shape

```jsonc
{ "success": true, "data": { "items": [ { "id", "action", "payload", "createdAt" } ] } }
```

The `payload` shape is module-specific and validated at enqueue time
against the per-module Zod schema:

| Module | Table | Payload schema | Actions |
|---|---|---|---|
| iCal | `macsync.calendar_send_queue` | `calendarSendPayloadSchema` | `create_event`, `update_event`, `delete_event` |
| iMail | `macsync.mail_send_queue` | `mailSendPayloadSchema` | `send_mail` |
| iNotes | `macsync.note_send_queue` | `noteSendPayloadSchema` | `create_note`, `update_note`, `delete_note` |
| iReminders | `macsync.reminder_send_queue` | `reminderSendPayloadSchema` | `create_reminder`, `update_reminder`, `delete_reminder`, `complete_reminder` |

iMessage retains the legacy `icloud.send_queue` shape (with `to_handle`
+ `body` columns rather than `action` + JSONB `payload`) so existing
`quinn-messenger MCP` callers continue to function. New modules adopted
the cleaner action/payload shape from the start, and live in the new
`macsync` schema rather than `icloud` (the schema rename landed 2026-05-15
to keep "iMessage / iCloud" sync state separate from "MacSync's own
operational queues").

## Implementation Location

- **Admin endpoint (iMessage)**: `src/surfaces/admin/send-queue.ts`
- **Scheduled-send admin endpoints**: `src/surfaces/admin/scheduled-send.ts`
- **Client endpoints (iMessage)**: `src/surfaces/client/imessage.ts`
- **Client endpoints (iCal)**: `src/surfaces/client/ical.ts`
- **Client endpoints (iMail)**: `src/surfaces/client/imail.ts`
- **Client endpoints (iNotes)**: `src/surfaces/client/inotes.ts`
- **Client endpoints (iReminders)**: `src/surfaces/client/ireminders.ts`
- **Schema (iMessage legacy)**: `src/entities/send-queue/schema.ts`
- **Schema (per-module)**: `src/entities/{calendar,mail,note,reminder}SendItem/schema.ts`
- **Shared repo factory**: `src/shared/sendQueue/SendQueueRepo.ts`
- **Shared types**: `src/entities/send-queue/types.ts`
