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

# Send a Message

> Send an iMessage and track delivery

## Send a message

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  import { createClient } from "@messages-dev/sdk";

  const client = createClient();

  await client.sendMessage({
    from: "+15551234567",
    to: "+15559876543",
    text: "Hello from Messages.dev!",
  });
  ```

  ```bash curl theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
  curl -X POST "https://api.messages.dev/v1/messages" \
    -H "Authorization: Bearer sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "from": "+15551234567",
      "to": "+15559876543",
      "text": "Hello from Messages.dev!"
    }'
  ```
</CodeGroup>

| Field         | Required    | Description                                                                                            |
| ------------- | ----------- | ------------------------------------------------------------------------------------------------------ |
| `from`        | Yes         | The line handle to send from                                                                           |
| `to`          | Yes         | Recipient phone number, Apple ID, or chat ID (`cht_...` for [group chats](/guides/group-chats))        |
| `text`        | Conditional | Message text. Required unless `attachments` is set.                                                    |
| `attachments` | Conditional | Array of file IDs (max 1) from [`POST /v1/files`](/guides/attachments). Required unless `text` is set. |
| `reply_to`    | No          | Message ID (`msg_...`) or iMessage GUID to thread the message as a reply.                              |

## Reply to a specific message

Pass the original message's `id` or `guid` as `replyTo` to thread your reply
underneath it in the recipient's conversation:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
await client.sendMessage({
  from: "+15551234567",
  to: "+15559876543",
  text: "Got it!",
  replyTo: "msg_abc123",
});
```

## Group chats

Pass a chat ID (`cht_...`) returned by [`listChats`](/api-reference/chats/list-chats)
as `to`:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
await client.sendMessage({
  from: "+15551234567",
  to: "cht_abc123",
  text: "Hey team!",
});
```

The contact-first rule below applies to group chats too — the chat must have
at least one inbound message before you can send into it. See
[Group chats](/guides/group-chats) for the full flow.

## Contact-first restriction

To keep lines healthy with Apple's spam detection, you can only send to a
contact (or group chat) **after** they have messaged your line first. Trying
to send first returns:

```json theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
{
  "error": {
    "type": "invalid_request_error",
    "code": "contact_has_not_messaged",
    "message": "Cannot send to a contact who has not messaged this line first.",
    "param": "to"
  }
}
```

The sandbox line is exempt once activated — your paired phone number is
treated as having messaged in. For production lines, drive inbound traffic
via marketing channels (a tap-to-text link, a vCard with your line saved as a
contact, etc.) before you can reply.

## Track delivery

Register a webhook for `message.sent` and your server will receive a POST when
the message is delivered:

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
import { verifyWebhook } from "@messages-dev/sdk";

app.post("/webhooks", async (req, res) => {
  const event = await verifyWebhook(
    req.body,
    req.headers["x-webhook-signature"],
    "your_webhook_secret",
  );

  if (event.event === "message.sent") {
    console.log(`Delivered: ${event.data.id}`);
  }

  res.sendStatus(200);
});
```

| Status    | Meaning                                   |
| --------- | ----------------------------------------- |
| `pending` | Queued for delivery                       |
| `claimed` | Being processed                           |
| `sent`    | Delivered                                 |
| `failed`  | Delivery failed (check the `error` field) |

## Errors

| Status                                               | Code                         | Cause                                                                  |
| ---------------------------------------------------- | ---------------------------- | ---------------------------------------------------------------------- |
| 400                                                  | `missing_required_parameter` | Missing `from`, `to`, or both `text` and `attachments`                 |
| 400                                                  | `invalid_parameter_value`    | `attachments` is malformed (e.g. wrong ID prefix or more than 1 entry) |
| 401                                                  | `missing_api_key`            | No `Authorization` header                                              |
| 403                                                  | `insufficient_scope`         | Key lacks `messages:write` scope                                       |
| 403                                                  | `line_not_accessible`        | Key can't access this line                                             |
| 403                                                  | `contact_has_not_messaged`   | The recipient (or chat) hasn't messaged your line first                |
| 404                                                  | `line_not_found`             | Invalid line handle                                                    |
| 404                                                  | `chat_not_found`             | The `cht_...` ID doesn't exist on this line                            |
| 404                                                  | `file_not_found`             | An attachment file ID doesn't exist                                    |
| 404                                                  | `message_not_found`          | `reply_to` references a message that doesn't exist                     |
| With the SDK, errors are thrown as typed exceptions: |                              |                                                                        |

```typescript theme={"theme":{"light":"github-light","dark":"github-dark-high-contrast"}}
import { createClient, InvalidRequestError } from "@messages-dev/sdk";

try {
  await client.sendMessage({ from: "+15551234567", to: "+1555...", text: "Hi" });
} catch (err) {
  if (err instanceof InvalidRequestError) {
    console.error(err.code, err.param);
  }
}
```
