> This page is for Omni Channel.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.hams.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.hams.ai/_mcp/server.

# Messages

> How to address a message, and how the service window controls whether the API accepts it.

All sends go through 1 endpoint:

```
POST /api/v1/accounts/{account_id}/developer/messages
```

The request needs `content`, or `template_params`, or both. For a template, see [Templates](/omni-channel/api/templates).

## 3 ways to address a message

Give exactly 1 identifier. If a request has more than 1, the API reads them in the order below. It ignores the others.

#### conversation_id: reply in a conversation that you know

```json
{ "conversation_id": 1234, "content": "It is on its way." }
```

`conversation_id` is the conversation number that your agents see in the dashboard URL. It is the `display_id` of the conversation, not its internal `id`. The conversation gives the inbox, so you do not send `inbox_id`. A number that is not in your account returns `404`.

#### contact_id: send to a contact that you have

```json
{ "contact_id": 42, "inbox_id": 5, "content": "It is on its way." }
```

The API uses the most recent conversation of the contact on that inbox with a status other than `resolved`. If there is none, it opens a new conversation. The contact must have an address that the channel can reach: a phone number for WhatsApp or SMS, an email address for email. If the contact has no such address, the API returns `contact_not_messageable`.

#### phone_number: send to a number that has no contact yet

```json
{
  "phone_number": "+971500000000",
  "inbox_id": 5,
  "content": "Hi, your order 121212 is confirmed",
  "contact": { "name": "Omar A", "custom_attributes": { "crm_id": "9912" } }
}
```

The number must be in E.164 format: a `+`, the country code, then digits, with no spaces or dashes. If no contact has that number, the API creates a contact. Only then does it use the optional `contact` fields: `name`, `email`, and `custom_attributes`. This way works on WhatsApp, Twilio, and SMS inboxes only. Other inboxes return `unsupported_channel`.

## The service window

Some channels let a business send free text only for a period after the last message of the customer. This period is the service window. After the window closes, the API accepts only a template. The API checks the window before it accepts the request. If the window has closed, the API returns this error immediately:

```json
{
  "error": "outside_messaging_window",
  "message": "The customer service window is closed for this conversation. Send a template message instead."
}
```

| Channel                                                                 | Window after the last message of the customer                                 |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| WhatsApp                                                                | 24 hours                                                                      |
| Twilio with the WhatsApp medium                                         | 24 hours                                                                      |
| Facebook and Instagram                                                  | 24 hours, or 7 days when Hams support turns on the Meta human agent option    |
| TikTok                                                                  | 48 hours                                                                      |
| API inbox                                                               | The reply time window of the inbox, in hours. No window if the inbox has none |
| Website, email, SMS, Twilio with the SMS medium, and all other channels | No window                                                                     |
| Phone inbox                                                             | Always closed                                                                 |

> **Warning**
>
> Send a template for the first message to a new contact on a channel with a window. A conversation with no message from the customer has no open window. The API rejects plain `content` to that conversation with `outside_messaging_window`.

The Phone inbox holds the calls from Cloud Call Center. It cannot send a message to the customer, so the API rejects every send to it with `422`. A private note through the conversation endpoint is the exception. See [Phone calls](/omni-channel/voice).

To know if the window is open before you send, read the conversation. Its `can_reply` field is `true` when the window is open. See [Conversations](/omni-channel/api/conversations).

> **Warning**
>
> Send `template_params` only on an inbox that has approved templates. When `template_params` is present, the API does not check the window, on any channel. On a channel without templates, the API sends `content` as free text, and only the provider enforces the window.

## Conversation reuse

An API send goes into the same conversation as the replies of the customer. The API uses the most recent conversation with a status other than `resolved`. If all the conversations have the status `resolved`, the API opens a new conversation. If the inbox has the option to keep 1 conversation for each contact, the API always uses the latest conversation, resolved or not.

A second send to the same number continues the same conversation. Your agents see 1 conversation, not 2.

## Response

The API answers `201` with the message:

| Field             | Meaning                                                                  |
| ----------------- | ------------------------------------------------------------------------ |
| `id`              | The id of the message                                                    |
| `conversation_id` | The conversation number in the dashboard URL (`display_id`)              |
| `contact_id`      | The id of the contact                                                    |
| `inbox_id`        | The id of the inbox                                                      |
| `status`          | The delivery status at the time of the response. A new message is `sent` |
| `content`         | The text of the message                                                  |
| `created_at`      | The time of the message, as a Unix timestamp in seconds                  |

## Partial effects

The API finds or creates the contact and the conversation before it checks the window. A request that fails with `outside_messaging_window` can leave a new contact and a new conversation. This is safe. A second request with a template uses the same contact and conversation. It does not make duplicates.

## Related

* [Templates](/omni-channel/api/templates)
* [Conversations](/omni-channel/api/conversations)
* [Errors](/omni-channel/api/errors)