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

# Send Raw Bulk Messages

> Send multiple raw SMS messages in bulk with optional skip flags for advanced control.

Send up to 1,000 raw SMS messages in a single API call. Each message in the batch is validated independently -- successfully validated messages are queued even if others in the batch fail. Each message object accepts a set of `skip_*` flags intended for granular control over validation and rate-limiting behavior.

<Warning>
  **The per-message `skip_*` flags are currently accepted but have no effect.** The endpoint does not read them, so every check runs regardless of what you pass. Until the flags are wired up, this endpoint behaves the same as [`POST /v2/send/bulk`](/api-reference/v2/messages/send-bulk) except that it does not perform link wrapping or contact-data macro substitution.
</Warning>

<Warning>
  The following checks are **always enforced**:

  * Your account must own the sending list
  * Phone numbers must be in E.164 format
  * Your account must be in active status
</Warning>

## Body Parameters

<ParamField body="messages" type="array" required>
  Array of raw message objects (maximum 1,000 per request). Each object supports the following fields:

  <Expandable title="Message object fields">
    <ParamField body="to" type="string" required>
      Recipient phone number in E.164 format (e.g. `+14155551234`).
    </ParamField>

    <ParamField body="list_number" type="string" required>
      Sending list phone number in E.164 format. Must belong to your account.
    </ParamField>

    <ParamField body="body" type="string" required>
      Message body text. Use the `{{messageId}}` placeholder to insert the unique message ID into the body at send time.
    </ParamField>

    <ParamField body="skip_duplicate_check" type="boolean" default="false">
      Intended to skip the duplicate message check. **Currently accepted but ignored — this flag has no effect.**
    </ParamField>

    <ParamField body="skip_rate_limit" type="boolean" default="false">
      Intended to skip per-list rate limiting. **Currently accepted but ignored — this flag has no effect.**
    </ParamField>

    <ParamField body="skip_contact_validation" type="boolean" default="false">
      Intended to skip contact validation such as opt-out and block list checks. **Currently accepted but ignored — this flag has no effect.**
    </ParamField>

    <ParamField body="skip_journey_check" type="boolean" default="false">
      Intended to skip the journey enrollment check. **Currently accepted but ignored — this flag has no effect.**
    </ParamField>

    <ParamField body="metadata" type="object">
      Optional key-value metadata dictionary. **Currently accepted but not stored or returned** — the endpoint discards this field, so it cannot yet be used for correlation.
    </ParamField>
  </Expandable>
</ParamField>

## Response Fields

<ResponseField name="queued_count" type="integer">
  Number of messages successfully queued for delivery.
</ResponseField>

<ResponseField name="error_count" type="integer">
  Number of messages that failed validation or queueing.
</ResponseField>

<ResponseField name="errors" type="array">
  Array of error objects for messages that failed validation or queueing. Each object contains:

  <Expandable title="Error object fields">
    <ResponseField name="index" type="integer">
      Zero-based index of the failed message in the original `messages` array.
    </ResponseField>

    <ResponseField name="to" type="string">
      The recipient phone number from the failed message, if provided.
    </ResponseField>

    <ResponseField name="code" type="string">
      Machine-readable error code identifying the failure.
    </ResponseField>

    <ResponseField name="error" type="string">
      Human-readable error description.
    </ResponseField>

    <ResponseField name="errorCode" type="string">
      Present only when `code` is `kafka_producer_failure`. Constant value `KAFKA_PRODUCER_FAILURE`, indicating the message passed validation but could not be published to the delivery queue.
    </ResponseField>

    <ResponseField name="message_id" type="string">
      Present only when `code` is `kafka_producer_failure`. The message ID that was assigned before the publish failure.
    </ResponseField>
  </Expandable>
</ResponseField>

## Examples

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.tracklysms.com/api/v2/send-raw/bulk \
    -H "X-Api-Key: trk_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "messages": [
        {
          "to": "+14155551234",
          "list_number": "+18005551000",
          "body": "Verification code: 482901. Ref: {{messageId}}",
          "skip_duplicate_check": true,
          "skip_rate_limit": true
        },
        {
          "to": "+14155559876",
          "list_number": "+18005551000",
          "body": "Verification code: 739204. Ref: {{messageId}}",
          "skip_duplicate_check": true,
          "skip_rate_limit": true,
          "skip_contact_validation": true
        }
      ]
    }'
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.tracklysms.com/api/v2/send-raw/bulk",
      headers={
          "X-Api-Key": "trk_your_api_key_here",
          "Content-Type": "application/json",
      },
      json={
          "messages": [
              {
                  "to": "+14155551234",
                  "list_number": "+18005551000",
                  "body": "Verification code: 482901. Ref: {{messageId}}",
                  "skip_duplicate_check": True,
                  "skip_rate_limit": True,
              },
              {
                  "to": "+14155559876",
                  "list_number": "+18005551000",
                  "body": "Verification code: 739204. Ref: {{messageId}}",
                  "skip_duplicate_check": True,
                  "skip_rate_limit": True,
                  "skip_contact_validation": True,
              },
          ]
      },
  )

  data = response.json()
  print(f"Queued: {data['queued_count']}, Errors: {data['error_count']}")
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.tracklysms.com/api/v2/send-raw/bulk", {
    method: "POST",
    headers: {
      "X-Api-Key": "trk_your_api_key_here",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      messages: [
        {
          to: "+14155551234",
          list_number: "+18005551000",
          body: "Verification code: 482901. Ref: {{messageId}}",
          skip_duplicate_check: true,
          skip_rate_limit: true,
        },
        {
          to: "+14155559876",
          list_number: "+18005551000",
          body: "Verification code: 739204. Ref: {{messageId}}",
          skip_duplicate_check: true,
          skip_rate_limit: true,
          skip_contact_validation: true,
        },
      ],
    }),
  });

  const data = await response.json();
  console.log(`Queued: ${data.queued_count}, Errors: ${data.error_count}`);
  ```
</RequestExample>

<ResponseExample>
  ```json Success (201) theme={null}
  {
    "queued_count": 2,
    "error_count": 0,
    "errors": []
  }
  ```

  ```json Partial Success (201) theme={null}
  {
    "queued_count": 1,
    "error_count": 1,
    "errors": [
      {
        "index": 1,
        "to": "+14155559876",
        "code": "list_not_found",
        "error": "Sending list not found for this list_number"
      }
    ]
  }
  ```

  ```json Error (400) theme={null}
  {
    "error": "messages array is required",
    "code": "missing_messages"
  }
  ```

  ```json Error (413) theme={null}
  {
    "error": "Maximum 1000 messages per request",
    "code": "too_many_messages"
  }
  ```
</ResponseExample>

## Error Codes

### Request-Level Errors

| HTTP Status | Error Code          | Description                                            |
| ----------- | ------------------- | ------------------------------------------------------ |
| 400         | `missing_messages`  | The `messages` array is required but was not provided. |
| 413         | `too_many_messages` | The `messages` array exceeds the 1,000 message limit.  |

Individual messages that fail are reported per-message (below) and the request still returns `201`. The request returns `502` only when **every** message fails to queue for delivery. Authenticated requests can also fail with `401 invalid_credentials`, `403 account_suspended`, or `429 rate_limited` — see [Error Codes](/api-reference/v2/error-codes).

### Per-Message Errors

Returned inside each failed entry of the response's `errors[]` array (as `code`), with the request status `201`.

| Code                     | Description                                                                                |
| ------------------------ | ------------------------------------------------------------------------------------------ |
| `missing_to`             | The `to` field is required but was not provided.                                           |
| `missing_list_number`    | The `list_number` field is required but was not provided.                                  |
| `missing_body`           | The `body` field is required but was not provided.                                         |
| `invalid_phone`          | The `to` field is not a valid E.164 phone number.                                          |
| `invalid_list_number`    | The `list_number` field is not a valid E.164 phone number.                                 |
| `list_not_found`         | The sending list was not found or does not belong to your account.                         |
| `webhook_not_configured` | Webhook verification required before sending (BYOC lists).                                 |
| `pending_confirmation`   | The recipient has not confirmed their double opt-in yet, so they cannot be messaged.       |
| `doi_expired`            | The recipient never confirmed their double opt-in and the confirmation window has expired. |
| `warmup_limit`           | The recipient is not yet eligible to be messaged under the list's warm-up schedule.        |
| `kafka_producer_failure` | The message could not be queued for delivery (transient); retry the failed records.        |

## Next Steps

<CardGroup cols={2}>
  <Card title="Campaign Execution" icon="rocket" href="/guides/campaigns/execution">
    How messages flow from queue to delivery
  </Card>

  <Card title="Create Contact" icon="user-plus" href="/api-reference/v2/contacts/create-contact">
    Add contacts before sending
  </Card>
</CardGroup>
