# Inbox

> Multi-channel inbox: list threads, generate AI drafts, send replies via email, SMS, or LinkedIn

Source: https://docs.graph8.com/developers/inbox/

The Inbox API exposes graph8's unified inbox across **email**, **SMS**, and **LinkedIn** (HeyReach) in a single thread shape. Use it to build replies and assignments into your own surface, or to power AI agents that triage inbound messages.

The `channel` query parameter or request field always uses the developer-facing values:

| Channel value | Internal channel |
|---------------|------------------|
| `email` | email |
| `sms` | sms |
| `linkedin` | heyreach |

Threads on the LinkedIn channel return `channel: "linkedin"` in responses.

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/inbox` | GET | List threads across channels |
| `/inbox/{reply_id}` | GET | Get a single thread |
| `/inbox/{reply_id}/assign` | POST | Assign a user to a thread |
| `/inbox/{reply_id}/tag` | POST | Add tags to a thread |
| `/inbox/{reply_id}/draft` | GET | Generate an AI draft (charges credits) |
| `/inbox/{reply_id}/send` | POST | Send a reply on a channel |

For machine-readable schemas see the [interactive API docs](https://be.graph8.com/api/v1/docs).

> **Caution: Use a real thread id**
> Every `{reply_id}` below is the `id` of a thread returned by [`GET /inbox`](#list-inbox-threads). Thread ids are opaque strings (a Gmail thread id, an email Message-ID, and so on), so there is no format to construct one from: always read it from the list. The examples store it in `$THREAD_ID` first. The ids in the sample responses on this page are illustrative only; sending one returns `422`.
>
> ```bash
> THREAD_ID=$(curl -s "https://be.graph8.com/api/v1/inbox?channel=email&page_size=1" \
>   -H "Authorization: Bearer $API_KEY" | jq -r '.data[0].id')
> ```

---

## List Inbox Threads

`GET /inbox`

Returns paginated threads across all channels (or a single channel via filter).

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `channel` | string | - | `email`, `sms`, or `linkedin`. Omit for all |
| `sequence_id` | string | - | Filter by sequence (campaign) ID |
| `status` | string | - | Filter by thread status |
| `assignee` | string | - | Filter to threads assigned to this email |
| `tag` | string | - | Filter by tag ID |
| `exclude_tag_ids` | string[] | - | Array of tag IDs to exclude from results. Threads that carry any of the listed tags are hidden. Pass the parameter multiple times (or as a comma-separated list) to exclude several tags at once. |
| `page` | integer | 1 | Page number (>= 1) |
| `page_size` | integer | 50 | 1-100 |

### Example

**cURL**

```bash
curl "https://be.graph8.com/api/v1/inbox?channel=email&page_size=20" \
  -H "Authorization: Bearer $API_KEY"
```

**Python**

```python
response = requests.get(
    f"{BASE_URL}/inbox",
    headers=HEADERS,
    params={"channel": "email", "page_size": 20},
)
threads = response.json()["data"]
```

### Response

```json
{
  "data": [
    {
      "id": "<thread id: pass this as {reply_id}>",
      "channel": "email",
      "subject": "Re: Q2 outbound",
      "contact": {"email": "jane@acme.com", "name": "Jane Smith", "company": "Acme Corp"},
      "messages": [
        {
          "message_id": "msg_001",
          "from_address": "jane@acme.com",
          "to_addresses": ["rep@graph8.com"],
          "content": "Sounds great - let's chat Tuesday.",
          "responder": "OTHER",
          "date": "2026-04-15T14:30:00Z",
          "is_draft": false
        }
      ],
      "status": "open",
      "tags": [{"id": "tag_1", "name": "interested"}],
      "assignees": [{"email": "rep@graph8.com", "name": "Sales Rep"}],
      "created_at": "2026-04-15T14:30:00Z",
      "updated_at": "2026-04-15T14:30:00Z"
    }
  ],
  "pagination": {"page": 1, "limit": 20, "total": 1, "has_next": false}
}
```

The `responder` field on a message is `USER` (a graph8 user replied), `AI` (graph8 sent an AI-drafted reply), or `OTHER` (the contact). `status` is `open`, `responded`, or `ai_responded`.

> **Note: Sentinel tag IDs**
> graph8 reserves a set of well-known UUIDs for internal system tags (Warmup, Bounced, Archived, Deleted). These are automatically excluded from the default Focused view via `exclude_tag_ids`. When building your own integrations, pass the org-specific tag IDs returned by your tags endpoint. If your org has not overridden them, the platform falls back to sentinel UUIDs of the form `00000000-0000-0000-0000-00000000000N`.

---

## Get Thread

`GET /inbox/{reply_id}`

Returns one thread by ID. Pass the matching `channel` query parameter so the right backing store is queried.

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `channel` | string | `email` | `email`, `sms`, or `linkedin` |

### Example

**cURL**

```bash
curl "https://be.graph8.com/api/v1/inbox/$THREAD_ID?channel=email" \
  -H "Authorization: Bearer $API_KEY"
```

### Errors

| Status | Meaning |
|--------|---------|
| `400` | Invalid `reply_id` (null/undefined/empty rejected early) |
| `422` | `reply_id` is an example id from this page, not a real thread |

---

## Assign Reply

`POST /inbox/{reply_id}/assign`

Assign a user (by email) to the thread.

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `assignee_email` | string | **Yes** | Email of the user to assign |

### Example

**cURL**

```bash
curl -X POST "https://be.graph8.com/api/v1/inbox/$THREAD_ID/assign?channel=email" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"assignee_email": "rep@graph8.com"}'
```

### Response

```json
{ "data": { "assigned": true, "assignee": "rep@graph8.com", "count": 1 } }
```

---

## Tag Reply

`POST /inbox/{reply_id}/tag`

Attach one or more tags to a thread. Tags must already exist in your workspace - this endpoint does not create them.

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `channel` | string | `email` | `email`, `sms`, or `linkedin` |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `tag_ids` | string[] | **Yes** | Tag IDs to associate |

### Example

**cURL**

```bash
curl -X POST "https://be.graph8.com/api/v1/inbox/$THREAD_ID/tag?channel=email" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tag_ids": ["tag_interested", "tag_q2"]}'
```

### Response

```json
{ "data": { "tagged": true, "tag_count": 2 } }
```

---

## Generate AI Draft

`GET /inbox/{reply_id}/draft`

Generate an AI-drafted reply for the thread. **This charges credits.** A `402 Payment Required` is returned when the org has insufficient credits.

### Query Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `channel` | string | `email` | Channel of the source thread |

### Example

**cURL**

```bash
curl "https://be.graph8.com/api/v1/inbox/$THREAD_ID/draft?channel=email" \
  -H "Authorization: Bearer $API_KEY"
```

### Response

```json
{
  "data": {
    "content": "<p>Tuesday at 10am works great...</p>",
    "plain_content": "Tuesday at 10am works great...",
    "credits_charged": 1
  }
}
```

### Errors

| Status | Meaning |
|--------|---------|
| `402` | Insufficient credits for draft generation |
| `404` | No thread with this `reply_id` on this `channel`; no credits are charged |
| `422` | `reply_id` is an example id from this page, not a real thread |

---

## Send Reply

`POST /inbox/{reply_id}/send`

Send a reply on the chosen channel. The required fields differ per channel.

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `body` | string | **Yes** | Message body (HTML for email; plain text for SMS/LinkedIn) |
| `channel` | string | **Yes** | `email`, `sms`, or `linkedin` |
| `subject` | string | Email only | Email subject |
| `to` | string | Email/SMS | Recipient address or phone number |
| `from_address` | string | No | Email mailbox, Twilio number, or LinkedIn account ID |

For LinkedIn, `from_address` must be a numeric LinkedIn account ID.

`$THREAD_ID`, `$SMS_THREAD_ID` and `$LINKEDIN_THREAD_ID` are thread ids read from `GET /inbox` with `channel=email`, `sms` and `linkedin` respectively (see [Use a real thread id](#list-inbox-threads) above).

### Examples

**Email**

```bash
curl -X POST "https://be.graph8.com/api/v1/inbox/$THREAD_ID/send" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "email",
    "body": "<p>Tuesday at 10am works.</p>",
    "subject": "Re: Q2 outbound",
    "to": "jane@acme.com",
    "from_address": "rep@graph8.com"
  }'
```

**SMS**

```bash
curl -X POST "https://be.graph8.com/api/v1/inbox/$SMS_THREAD_ID/send" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "sms",
    "body": "Confirmed for Tuesday 10am.",
    "to": "+15555550100",
    "from_address": "+15555550199"
  }'
```

**LinkedIn**

```bash
curl -X POST "https://be.graph8.com/api/v1/inbox/$LINKEDIN_THREAD_ID/send" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "linkedin",
    "body": "Thanks for connecting!",
    "from_address": "12345"
  }'
```

### Response

```json
{
  "data": {
    "message_id": "msg_outbound_001",
    "status": "sent",
    "channel": "email"
  }
}
```

### Errors

| Status | Meaning |
|--------|---------|
| `400` | Missing `to` or `from_address` for the channel; LinkedIn `from_address` not numeric; unsupported channel |
| `403` | Your key may not send as `from_address` |
| `422` | `reply_id` is an example id from this page, not a real thread; or the provider rejected a recipient address as malformed |
