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

# Working with messages

> Send, read, edit, delete and react to messages

## Send a message

One endpoint sends every message: `POST /messages`. The `target` says where.

### Targets

| `target.type` | Fields | Sends to |
| - | - | - |
| `thread` | `threadId` | An existing conversation |
| `contact` | `accountLinkId`, `contactId` | The contact's profile on the account link's platform |
| `profile` | `accountLinkId`, `platform`, `platformId` | A platform user, by their ID on that platform |

`contact` and `profile` targets use the existing thread or start a new one. The response always returns the thread.

<CodeGroup>
  ```typescript send.ts theme={null}
  import { randomUUID } from "node:crypto";

  type Target =
    | { type: "thread"; threadId: string }
    | { type: "contact"; accountLinkId: string; contactId: string }
    | {
        type: "profile";
        accountLinkId: string;
        platform: string;
        platformId: string;
      };

  async function send(target: Target, content: string) {
    const response = await fetch("https://inboxapp.com/api/v2/messages", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.INBOX_API_TOKEN}`,
        "Content-Type": "application/json",
        "Idempotency-Key": randomUUID(),
      },
      body: JSON.stringify({ target, content }),
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`${error.code}: ${error.message} (${error.requestId})`);
    }

    return response.json();
  }

  const { message, thread } = await send(
    { type: "thread", threadId: "l44e15irdq4db30i77cgphhx" },
    "Thanks for the details, sending the proposal today.",
  );
  ```

  ```bash cURL theme={null}
  curl -X POST "https://inboxapp.com/api/v2/messages" \
    -H "Authorization: Bearer $INBOX_API_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 7f3c1d52-9a41-4a6e-8a55-0c1d2f6b9e10" \
    -d '{
      "target": { "type": "thread", "threadId": "l44e15irdq4db30i77cgphhx" },
      "content": "Thanks for the details, sending the proposal today."
    }'
  ```
</CodeGroup>

```json theme={null}
{
  "message": {
    "id": "p8rvk2m5j0xn4wq7ybftcael",
    "threadId": "l44e15irdq4db30i77cgphhx",
    "accountLinkId": "df6jbw4h36qm5d9iu2sgn7kx",
    "platform": "twitter",
    "platformId": "1967243189457100000",
    "direction": "outbound",
    "sentBy": { "type": "api", "tokenId": "k2m5j0xn4wq7ybftcaelp8rv" },
    "content": "Thanks for the details, sending the proposal today.",
    "attachments": [],
    "reactions": [],
    "editedAt": null,
    "deletedAt": null,
    "createdAt": "2026-09-14T10:32:00.000Z"
  },
  "thread": { "id": "l44e15irdq4db30i77cgphhx" }
}
```

To reply to a specific message, add `replyToMessageId`. Needs `message:reply`.

### Send safely

A network failure leaves you not knowing whether the message went out. Send an `Idempotency-Key` header, and retry with the same key: a replay returns the original result with `200` instead of sending again.

| Response | What to do |
| - | - |
| `201` | Sent |
| `200` | A replay of a key you already used. Same result as the first |
| `409 sendInProgress` | The first attempt is still running. Retry shortly |
| `422 idempotencyKeyReused` | The key was used with a different body. Use a new key |
| `502 sendUnconfirmed` | The platform may have delivered it. Don't retry with a new key |
| `429`, `503` | Wait for `Retry-After`, then retry with the same key |

Keys are scoped to your team and kept for 24 hours.

### Why a send fails

| Code | Meaning |
| - | - |
| `accountLinkDisconnected` | The account is `offline` or `paused`. See `details.status` |
| `recipientUnavailable` | The person can't receive messages from this account |
| `contentRejected` | Too long for the platform's `limits.characters`, or refused |
| `threadRestricted` | The platform closed the conversation to sends |
| `profileAmbiguous` | The contact has several profiles on this platform. Use a `profile` target |
| `planRequired` | Messaging a non-contact needs the Advanced API add-on |

## List messages

```bash theme={null}
curl "https://inboxapp.com/api/v2/threads/l44e15irdq4db30i77cgphhx/messages?limit=50" \
  -H "Authorization: Bearer $INBOX_API_TOKEN"
```

Newest first. Pass `order=asc` for oldest first, and `nextCursor` as `cursor` for the next page.

While a thread's `syncing` is true, Inboxapp is still importing its history and older messages may be missing.

### Who sent it

`direction` is `inbound` or `outbound`. For outbound messages, `sentBy` says who on your side:

| `sentBy.type` | Sent by |
| - | - |
| `member` | A teammate, in the Inboxapp app |
| `agent` | An AI tool connected through MCP |
| `campaign` | A campaign step |
| `api` | An API token |
| `null` | Inbound, or sent outside Inboxapp |

### Attachments

`attachments` is a list tagged by `kind`: `image`, `gif`, `video`, `audio`, `file` or `card`.

A media attachment has a `status`. Its `url` is set only when `status` is `ready`, and it expires at `expiresAt`. Get a fresh one when you need it:

```bash theme={null}
curl "https://inboxapp.com/api/v2/messages/p8rvk2m5j0xn4wq7ybftcael/attachments/0" \
  -H "Authorization: Bearer $INBOX_API_TOKEN"
```

`200` returns the attachment with a usable `url`. `202` means it is still `pending` or `processing`: ask again later.

## Edit a message

```bash theme={null}
curl -X PATCH "https://inboxapp.com/api/v2/messages/p8rvk2m5j0xn4wq7ybftcael" \
  -H "Authorization: Bearer $INBOX_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "content": "Thanks for the details, sending the proposal tomorrow." }'
```

Needs `message:edit`. Platforms that allow edits usually limit how long after sending: past that, the request fails with `422 mutationWindowClosed`. The windows are listed in [Platforms and capabilities](/v2/platforms).

## Delete a message

`scope` is required:

| `scope` | Effect | Needs |
| - | - | - |
| `self` | Removes it for your account only | `message:delete:self` |
| `all` | Unsends it for everyone | `message:unsend` |

```bash theme={null}
curl -X DELETE "https://inboxapp.com/api/v2/messages/p8rvk2m5j0xn4wq7ybftcael?scope=all" \
  -H "Authorization: Bearer $INBOX_API_TOKEN"
```

## Reactions

```bash theme={null}
curl -X POST "https://inboxapp.com/api/v2/messages/p8rvk2m5j0xn4wq7ybftcael/reactions" \
  -H "Authorization: Bearer $INBOX_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "emoji": "👍" }'

curl -X DELETE "https://inboxapp.com/api/v2/messages/p8rvk2m5j0xn4wq7ybftcael/reactions?emoji=%F0%9F%91%8D" \
  -H "Authorization: Bearer $INBOX_API_TOKEN"
```

Both return the message's `reactions`. Each has an `author`: your `accountLink`, the other side's `profile`, or `unknown`. Needs `message:react`.

## Edit history

```bash theme={null}
curl "https://inboxapp.com/api/v2/messages/p8rvk2m5j0xn4wq7ybftcael/history" \
  -H "Authorization: Bearer $INBOX_API_TOKEN"
```

Returns every version of the content, the original first, and the deletion if there is one.


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