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

# Sending messages

> How to send messages through the Connecta DESK API: the four message types, the 202 response with a jobId and how to check the result.

`POST /messages` sends all four message types. The API responds right away with `202` and a
`jobId`, and sends the message in the background. You then check the `jobId` to find out whether
the message went out.

## The flow

<Steps>
  <Step title="Find the outbound channel">
    Every send needs a `channelId`. List the company's connected channels:

    ```bash theme={null}
    curl --request GET \
      --url "$CONNECTA_URL/channels" \
      --header "Authorization: Bearer $CONNECTA_TOKEN"
    ```

    Keep the id in your integration's configuration. It almost never changes, and fetching the list
    on every send uses up your [per-minute limit](/en/essentials/limites-de-uso) for no reason.
  </Step>

  <Step title="Send">
    ```bash theme={null}
    curl --request POST \
      --url "$CONNECTA_URL/messages" \
      --header "Authorization: Bearer $CONNECTA_TOKEN" \
      --header "Content-Type: application/json" \
      --header "Idempotency-Key: order-88213-confirmation" \
      --data '{
        "channelId": 4,
        "type": "text",
        "number": "5511999999999",
        "body": "Your order #88213 has been confirmed."
      }'
    ```

    The response comes back right away:

    ```json theme={null}
    {
      "data": { "jobId": "5512", "status": "queued" }
    }
    ```

    <Tip>
      Always send `Idempotency-Key` on this endpoint. If the call times out and you try again
      without the key, the customer may get the message twice. See
      [Idempotency](/en/essentials/idempotencia).
    </Tip>
  </Step>

  <Step title="Confirm the result">
    ```bash theme={null}
    curl --request GET \
      --url "$CONNECTA_URL/messages/jobs/5512" \
      --header "Authorization: Bearer $CONNECTA_TOKEN"
    ```

    ```json theme={null}
    {
      "data": {
        "jobId": "5512",
        "status": "sent",
        "attempts": 1,
        "ticketId": 9034,
        "providerMessageId": "wamid.HBgNNTUxMTk5OTk5OTk5ORUCABEYEjhBMkI…",
        "failedReason": null
      }
    }
    ```

    Keep the `ticketId`. With it you follow the conversation from then on, using
    `GET /tickets/{ticketId}/messages`.
  </Step>
</Steps>

## Job states

<ResponseField name="queued" type="status">
  Accepted and waiting to be processed.
</ResponseField>

<ResponseField name="processing" type="status">
  Being sent to the provider.
</ResponseField>

<ResponseField name="sent" type="status">
  Delivered to the provider. The `providerMessageId` field carries the identifier the provider gave
  the message.
</ResponseField>

<ResponseField name="failed" type="status">
  The send failed. `failedReason` explains why and `attempts` shows how many times the send was
  tried.
</ResponseField>

<Note>
  **Monthly active contacts.** The plan has a limit of active contacts per month: a contact counts
  once, at the first message exchanged with the company in the month (sent or received), and the
  count resets on the 1st. Once the limit is reached, contacts that already counted keep receiving
  normally; a message to a new contact is refused on the call with
  `402 ERR_API_ACTIVE_CONTACTS_LIMIT_REACHED`. If the limit runs out between the call and the send,
  the job ends as `failed` with `failedReason: ERR_MAX_ACTIVE_CONTACTS_BY_MONTH_REACHED`.
  `web_whatsapp` channels are not limited.
</Note>

<Warning>
  `sent` means the **provider accepted** the message. It does not yet mean the recipient received
  or read it. The delivery and read confirmation comes later, in the message's status inside the
  ticket.
</Warning>

When checking the job, start with a 1 second interval between checks and keep doubling it. Checking
over and over with no pause uses up the whole per-minute limit in a few seconds.

## The four types

<Tabs>
  <Tab title="text">
    This is the default type. Besides the recipient, only `body` is required.

    ```json theme={null}
    {
      "channelId": 4,
      "type": "text",
      "number": "5511999999999",
      "body": "Hi, {{name}}! Your protocol is 88213."
    }
    ```

    The `body` field accepts system variables, such as `{{name}}`.
  </Tab>

  <Tab title="media">
    The request must be sent as `multipart/form-data`, with the file attached. The `body` becomes
    the caption.

    ```bash theme={null}
    curl --request POST \
      --url "$CONNECTA_URL/messages" \
      --header "Authorization: Bearer $CONNECTA_TOKEN" \
      --form channelId=4 \
      --form type=media \
      --form number=5511999999999 \
      --form 'body=Here is the receipt.' \
      --form medias=@receipt.pdf
    ```
  </Tab>

  <Tab title="template">
    Required to start a conversation outside WhatsApp's service window. The channel's approved
    templates are at `GET /channels/{id}/templates`.

    ```json theme={null}
    {
      "channelId": 4,
      "type": "template",
      "number": "5511999999999",
      "template": {
        "name": "confirmacao_pedido",
        "parameters": ["Marina", "88213"]
      }
    }
    ```

    We recommend this form. You provide the name and the parameters, and the API checks them
    against the approved template and builds the message. If you already build the object with
    `components`, you can keep sending it that way.
  </Tab>

  <Tab title="interactive">
    Sends the `interactive` object of the official WhatsApp API: reply buttons (`button`), list
    (`list`), link button (`cta_url`), location request (`location_request_message`) and carousel
    (`carousel`), depending on what the channel supports.

    ```json theme={null}
    {
      "channelId": 4,
      "type": "interactive",
      "number": "5511999999999",
      "interactive": {
        "type": "button",
        "body": { "text": "Can we confirm your delivery for tomorrow?" },
        "action": {
          "buttons": [
            { "type": "reply", "reply": { "id": "yes", "title": "Yes" } },
            { "type": "reply", "reply": { "id": "no", "title": "Reschedule" } }
          ]
        }
      }
    }
    ```

    On **Webchat**, the five types above show up in the visitor's chat as they do on WhatsApp, and
    `interactive` is checked during the call itself, with the WhatsApp limits: a problem responds
    `422 ERR_API_INTERACTIVE_INVALID` right away, with the field and the reason. See
    [Webchat](/en/guides/webchat#interactive-messages).
  </Tab>
</Tabs>

## Who to send to

There are three ways to specify the recipient. If more than one is sent, this order applies:
`ticketId`, `contactId`, `number`.

<Columns cols={3}>
  <Card title="ticketId" icon="message-square">
    Sends within a ticket that already exists, and the message stays in the same conversation
    history. Use it to reply to a conversation in progress.
  </Card>

  <Card title="contactId" icon="user">
    Sends to a contact that already exists, on any channel. Required on channels without a phone
    number (Instagram, Facebook/Messenger, Webchat, Telegram): on them you can only message someone
    who has already talked to the channel. On Webchat, the visitor must have opened this channel's
    webchat.
  </Card>

  <Card title="number" icon="phone">
    The recipient's number, on phone channels. If the contact does not exist yet, it is created,
    and you can use `contactName` to give it a name. If there is no open ticket with that contact,
    a new one is opened.
  </Card>
</Columns>

## Controlling the ticket in the same send

Four optional fields change the ticket in the same call as the send, with no need for a second
request:

<ParamField body="queueId" type="integer">
  Moves the ticket to this queue after sending.
</ParamField>

<ParamField body="openTicket" type="boolean">
  Reopens the ticket if it is closed.
</ParamField>

<ParamField body="closeTicket" type="boolean">
  Closes the ticket after sending. Useful for notices that expect no reply.
</ParamField>

<ParamField body="wrapupTypeId" type="integer">
  Wrap-up type recorded when the ticket is closed. Use it together with `closeTicket`.
</ParamField>

## High volume

<Warning>
  Do not call `POST /messages` over and over to send in bulk. Each call consumes the whole company's
  [per-minute limit](/en/essentials/limites-de-uso), and pacing, retries and reporting are left up
  to you.

  That is what the **campaigns** module is for. It controls the outbound pace, lets you pause,
  resume and cancel the send, and shows a delivery report at the end.
</Warning>

<CardGroup cols={2}>
  <Card title="Idempotency" icon="repeat" href="/en/essentials/idempotencia">
    How to keep a retry from duplicating the message.
  </Card>

  <Card title="Messages reference" icon="code" href="/en/api-reference/introduction">
    Every accepted field, with a playground to test.
  </Card>
</CardGroup>


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