Sending Messages

This guide shows you how to send SMS, WhatsApp, and RCS messages with the Sent API, using a template or free-form text.

Overview

A Message in Sent represents a single communication sent to a contact through a specific channel (SMS, WhatsApp, or RCS). This guide covers the core aspects of message sending.

The Message Lifecycle

Sending Methods

1. Send to Phone Number

The simplest approach - send directly to any phone number:

curl -X POST "https://api.sent.dm/v3/messages" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["+1234567890"],
    "template": {
      "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
      "parameters": {
        "customerName": "John Doe",
        "orderNumber": "#12345"
      }
    }
  }'
import SentDm from '@sentdm/sentdm';

const client = new SentDm();

const response = await client.messages.send({
  to: ['+1234567890'],
  template: {
    id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
    parameters: {
      customerName: 'John Doe',
      orderNumber: '#12345'
    }
  }
});

console.log(`Message ID: ${response.data.recipients[0].message_id}`);
from sent_dm import SentDm

client = SentDm()

response = client.messages.send(
    to=["+1234567890"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        "parameters": {
            "customerName": "John Doe",
            "orderNumber": "#12345"
        }
    }
)

print(f"Message ID: {response.data.recipients[0].message_id}")
import (
    "context"
    "github.com/sentdm/sent-dm-go"
    "github.com/sentdm/sent-dm-go/option"
)

client := sentdm.NewClient()

response, err := client.Messages.Send(context.Background(), sentdm.MessageSendParams{
    To: []string{"+1234567890"},
    Template: sentdm.MessageSendParamsTemplate{
        ID: sentdm.String("7ba7b820-9dad-11d1-80b4-00c04fd430c8"),
        Parameters: map[string]interface{}{
            "customerName": "John Doe",
            "orderNumber":  "#12345",
        },
    },
})
import dm.sent.client.SentDmClient;
import dm.sent.client.okhttp.SentDmOkHttpClient;
import dm.sent.core.JsonValue;
import dm.sent.models.messages.MessageSendParams;

SentDmClient client = SentDmOkHttpClient.fromEnv();

MessageSendParams params = MessageSendParams.builder()
    .addTo("+1234567890")
    .template(MessageSendParams.Template.builder()
        .id("7ba7b820-9dad-11d1-80b4-00c04fd430c8")
        .parameters(MessageSendParams.Template.Parameters.builder()
            .putAdditionalProperty("customerName", JsonValue.from("John Doe"))
            .putAdditionalProperty("orderNumber", JsonValue.from("#12345"))
            .build())
        .build())
    .build();

var response = client.messages().send(params);
System.out.println("Sent: " + response.data().recipients().get(0).messageId());
using Sentdm;
using Sentdm.Models.Messages;
using System.Collections.Generic;

SentDmClient client = new();

MessageSendParams parameters = new()
{
    To = new List<string> { "+1234567890" },
    Template = new MessageSendParamsTemplate
    {
        Id = "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        Parameters = new Dictionary<string, string>
        {
            { "customerName", "John Doe" },
            { "orderNumber", "#12345" }
        }
    }
};

var response = await client.Messages.Send(parameters);
Console.WriteLine($"Sent: {response.Data.Recipients[0].MessageId}");
<?php
require_once 'vendor/autoload.php';

use SentDM\Client;

$client = new Client($_ENV['SENT_DM_API_KEY']);

$result = $client->messages->send(
    to: ['+1234567890'],
    template: [
        'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
        'parameters' => [
            'customerName' => 'John Doe',
            'orderNumber' => '#12345'
        ]
    ]
);

var_dump($result->data->recipients[0]->message_id);
require "sentdm"

sent_dm = Sentdm::Client.new

result = sent_dm.messages.send_(
  to: ["+1234567890"],
  template: {
    id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
    parameters: {
      customerName: "John Doe",
      orderNumber: "#12345"
    }
  }
)

puts result.data.recipients[0].message_id

2. Send to Multiple Recipients

Send the same message to multiple recipients in one request:

curl -X POST "https://api.sent.dm/v3/messages" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["+1234567890", "+1987654321", "+1555555555"],
    "template": {
      "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
      "parameters": {
        "announcement": "Our store is now open!"
      }
    }
  }'
const response = await client.messages.send({
  to: ['+1234567890', '+1987654321', '+1555555555'],
  template: {
    id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
    parameters: {
      announcement: 'Our store is now open!'
    }
  }
});

console.log(`Sent to ${response.data.recipients.length} recipients`);
response = client.messages.send(
    to=["+1234567890", "+1987654321", "+1555555555"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        "parameters": {
            "announcement": "Our store is now open!"
        }
    }
)

print(f"Sent to {len(response.data.recipients)} recipients")
response, err := client.Messages.Send(context.Background(), sentdm.MessageSendParams{
    To: []string{"+1234567890", "+1987654321", "+1555555555"},
    Template: sentdm.MessageSendParamsTemplate{
        ID: sentdm.String("7ba7b820-9dad-11d1-80b4-00c04fd430c8"),
        Parameters: map[string]interface{}{
            "announcement": "Our store is now open!",
        },
    },
})

fmt.Printf("Sent to %d recipients\n", len(response.Data.Recipients))
import dm.sent.models.messages.MessageSendParams;
import dm.sent.core.JsonValue;

MessageSendParams params = MessageSendParams.builder()
    .addTo("+1234567890")
    .addTo("+1987654321")
    .addTo("+1555555555")
    .template(MessageSendParams.Template.builder()
        .id("7ba7b820-9dad-11d1-80b4-00c04fd430c8")
        .parameters(MessageSendParams.Template.Parameters.builder()
            .putAdditionalProperty("announcement", JsonValue.from("Our store is now open!"))
            .build())
        .build())
    .build();

var response = client.messages().send(params);
System.out.println("Sent to " + response.data().recipients().size() + " recipients");
using Sentdm.Models.Messages;

MessageSendParams parameters = new()
{
    To = new List<string> { "+1234567890", "+1987654321", "+1555555555" },
    Template = new MessageSendParamsTemplate
    {
        Id = "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
        Parameters = new Dictionary<string, string>
        {
            { "announcement", "Our store is now open!" }
        }
    }
};

var response = await client.Messages.Send(parameters);
Console.WriteLine($"Sent to {response.Data.Recipients.Count} recipients");
$result = $client->messages->send(
    to: ['+1234567890', '+1987654321', '+1555555555'],
    template: [
        'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8',
        'parameters' => [
            'announcement' => 'Our store is now open!'
        ]
    ]
);

echo "Sent to " . count($result->data->recipients) . " recipients\n";
result = sent_dm.messages.send_(
  to: ["+1234567890", "+1987654321", "+1555555555"],
  template: {
    id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
    parameters: {
      announcement: "Our store is now open!"
    }
  }
)

puts "Sent to #{result.data.recipients.length} recipients"

Recipient Limit: A single request can include up to 1000 recipients. For campaigns larger than 1000, split into multiple requests. Each API call counts as one request toward the 10,000 req/min rate limit.

3. Send on Behalf of a Sender Profile

If your account is an organization with child Sender Profiles, an organization API key can send as any of its profiles by adding the x-profile-id header (the profile UUID). The request then executes as that profile, so its templates, contacts, sender numbers, and compliance settings apply, and the response echoes the scope in an X-Profile-Id header.

curl -X POST "https://api.sent.dm/v3/messages" \
  -H "x-api-key: $SENT_ORGANIZATION_API_KEY" \
  -H "x-profile-id: $PROFILE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["+1234567890"],
    "template": {
      "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
    }
  }'
const response = await client.messages.send({
  'x-profile-id': profileId,
  to: ['+1234567890'],
  template: { id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8' }
});
response = client.messages.send(
    x_profile_id=profile_id,
    to=["+1234567890"],
    template={"id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8"}
)
response, err := client.Messages.Send(context.Background(), sentdm.MessageSendParams{
    XProfileID: sentdm.String(profileID),
    To:         []string{"+1234567890"},
    Template: sentdm.MessageSendParamsTemplate{
        ID: sentdm.String("7ba7b820-9dad-11d1-80b4-00c04fd430c8"),
    },
})
MessageSendParams params = MessageSendParams.builder()
    .xProfileId(profileId)
    .addTo("+1234567890")
    .template(MessageSendParams.Template.builder()
        .id("7ba7b820-9dad-11d1-80b4-00c04fd430c8")
        .build())
    .build();

var response = client.messages().send(params);
MessageSendParams parameters = new()
{
    XProfileID = profileId,
    To = new List<string> { "+1234567890" },
    Template = new Template { ID = "7ba7b820-9dad-11d1-80b4-00c04fd430c8" }
};

var response = await client.Messages.Send(parameters);
$result = $client->messages->send(
    to: ['+1234567890'],
    template: ['id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8'],
    xProfileID: $profileId
);
result = sent_dm.messages.send_(
  x_profile_id: profile_id,
  to: ["+1234567890"],
  template: { id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8" }
)

Only organization API keys may send x-profile-id. A profile-scoped key is rejected with 403 (AUTH_004), a value that is not a UUID with 400 (VALIDATION_003), and a profile outside your organization with 404 (RESOURCE_013). Requests scoped this way draw from the organization's rate limit pool.

The alternative is to authenticate with the profile's own API key, which needs no header. Both patterns, plus per-language customer-to-profile mapping, are in Integrating Sender Profiles into Your Application; the provisioning flow is Create and activate Sender Profiles via the API.

Sending Free-Form Text (Without a Template)

To send an ad-hoc message body without creating a template, pass text instead of template. Provide exactly one of the two: the API rejects a request that includes both, or neither, with a 400 validation error.

curl -X POST "https://api.sent.dm/v3/messages" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["+1234567890"],
    "text": "Thanks for reaching out! Your order shipped this morning."
  }'
const response = await client.messages.send({
  to: ['+1234567890'],
  text: 'Thanks for reaching out! Your order shipped this morning.'
});

console.log(`Message ID: ${response.data.recipients[0].message_id}`);
response = client.messages.send(
    to=["+1234567890"],
    text="Thanks for reaching out! Your order shipped this morning."
)

print(f"Message ID: {response.data.recipients[0].message_id}")
response, err := client.Messages.Send(context.Background(), sentdm.MessageSendParams{
    To:   []string{"+1234567890"},
    Text: sentdm.String("Thanks for reaching out! Your order shipped this morning."),
})
MessageSendParams params = MessageSendParams.builder()
    .addTo("+1234567890")
    .text("Thanks for reaching out! Your order shipped this morning.")
    .build();

var response = client.messages().send(params);
MessageSendParams parameters = new()
{
    To = new List<string> { "+1234567890" },
    Text = "Thanks for reaching out! Your order shipped this morning."
};

var response = await client.Messages.Send(parameters);
$result = $client->messages->send(
    to: ['+1234567890'],
    text: 'Thanks for reaching out! Your order shipped this morning.'
);

var_dump($result->data->recipients[0]->message_id);
result = sent_dm.messages.send_(
  to: ["+1234567890"],
  text: "Thanks for reaching out! Your order shipped this morning."
)

puts result.data.recipients[0].message_id

To pin a free-form send to one channel, set channel exactly as for template sends; see Channel Selection Strategies. On SMS, a free-form body follows the same segmentation rules as template output; see SMS encoding and length.

The response to a free-form send still includes a template_id and template_name (FREE_TEXT_SYS_TEMPLATE). Free-form messages ride Sent's internal free-text system template, and your text renders verbatim as the message body.

When You Can Send Free-Form Text

Free-form text is for replying inside an open conversation. The first message to a contact you have no conversation with must use a template. Per channel:

ChannelFree-form text allowed
SMSIf the contact has ever replied on any channel, always. Otherwise within 7 days of the last approved template you sent them on any channel.
RCSSame rule as SMS.
WhatsApp (pinned with channel: "whatsapp")Only inside Meta's customer-service window: the contact must have sent an inbound WhatsApp message in the last 24 hours. Sent's rule does not apply, and a template never opens this window.
Auto-detect (channel omitted or ["sent"])Same rule as SMS and RCS. Sent does not route a free-form send to WhatsApp unless Meta's 24-hour window is also open; it falls back to RCS or SMS instead.

A reply from the contact is what makes free-form durable: once they have answered on any channel, free-form stays available on SMS, RCS, and auto-detect until they opt out. An outbound template only helps a contact who has never replied, opening a 7-day window on any channel. It never opens Meta's 24-hour WhatsApp window, which only an inbound WhatsApp message can open, so a WhatsApp-pinned free-form send needs a recent reply regardless of how many templates you sent. Your own free-form messages count for neither rule. Standalone opt-out keywords from the contact, such as STOP, do not count as a reply; START and HELP do. For how the two rules work, see Conversation Windows. To start a conversation with a contact who has never replied, use a template; see Working with Templates.

Free-Form Send Errors

If the request contains both text and template, or neither, the API rejects it synchronously:

{
  "success": false,
  "status": 400,
  "error": {
    "code": "VALIDATION_001",
    "message": "Request validation failed",
    "details": {
      "template": ["Provide exactly one of 'template' or 'text'"]
    },
    "doc_url": "https://docs.sent.dm/reference/api/error-catalog"
  }
}

If the request is valid but the send doesn't meet the per-channel rules, the API still returns 202 Accepted. The delivery pipeline enforces the gate, and the outcome is recorded on the message asynchronously:

  • SMS, RCS, or auto-detect to a contact who has never replied and has had no approved template in the last 7 days: status BLOCKED with error code CONVERSATION_TEMPLATE_REQUIRED.
  • WhatsApp pinned with channel: "whatsapp" outside Meta's 24-hour window: status FAILED with error code WHATSAPP_TEMPLATE_REQUIRED

Note the different terminal statuses. The conversation gate runs before routing and nothing is handed to a carrier, so it records BLOCKED and is excluded from your deliverability rate; it fires message.blocked, not message.failed. The WhatsApp gate still records FAILED.

Both call for an approved template as the first step, but what that buys you differs by channel. On SMS, RCS, and auto-detect a template makes free-form available for 7 days, and once the contact replies even once it stays available indefinitely. On a send pinned to channel: "whatsapp" the template itself delivers, but free-form stays gated until the contact replies within the last 24 hours, because only a recent inbound opens Meta's window; omit channel to reach the contact over RCS or SMS in the meantime. Neither code is returned on the response. Each arrives as the message's reason_code, on GET /v3/messages/{id} and on the webhook; see Status Tracking.

Channel Selection Strategies

Automatic Selection (Default)

Let Sent choose the optimal channel by omitting the channel field:

curl -X POST "https://api.sent.dm/v3/messages" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["+1234567890"],
    "template": {
      "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
    }
  }'
const response = await client.messages.send({
  to: ['+1234567890'],
  template: {
    id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8'
  }
  // No channel field - automatic selection
});
response = client.messages.send(
    to=["+1234567890"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
    }
    # No channel parameter - automatic selection
)
response, err := client.Messages.Send(context.Background(), sentdm.MessageSendParams{
    To: []string{"+1234567890"},
    Template: sentdm.MessageSendParamsTemplate{
        ID: sentdm.String("7ba7b820-9dad-11d1-80b4-00c04fd430c8"),
    },
    // No channel field - automatic selection
})
MessageSendParams params = MessageSendParams.builder()
    .addTo("+1234567890")
    .template(MessageSendParams.Template.builder()
        .id("7ba7b820-9dad-11d1-80b4-00c04fd430c8")
        .build())
    // No channel - automatic selection
    .build();

var response = client.messages().send(params);
MessageSendParams parameters = new()
{
    To = new List<string> { "+1234567890" },
    Template = new MessageSendParamsTemplate
    {
        Id = "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
    }
    // No channel - automatic selection
};

var response = await client.Messages.Send(parameters);
$result = $client->messages->send(
    to: ['+1234567890'],
    template: [
        'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8'
    ]
    // No channel - automatic selection
);
result = sent_dm.messages.send_(
  to: ["+1234567890"],
  template: {
    id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
  }
  # No channel - automatic selection
)

Sent considers:

  • Contact's available channels
  • Historical delivery success rates
  • Cost optimization
  • Regional preferences

Omitting the field is equivalent to channel: ["sent"], the auto-detect value. Automatic selection is also the only mode with cross-channel fallback: when the preferred channel can't deliver, Sent can route the message over another one.

Force Specific Channel

Override automatic selection by specifying the channel field. An explicit channel pins each message to that channel and disables cross-channel fallback: if the pinned channel can't deliver to a recipient, the message fails rather than switching channels.

The request shape is identical for every channel: only the channel value changes. This example pins the send to SMS:

curl -X POST "https://api.sent.dm/v3/messages" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["+1234567890"],
    "template": {
      "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
    },
    "channel": ["sms"]
  }'
const response = await client.messages.send({
  to: ['+1234567890'],
  template: {
    id: '7ba7b820-9dad-11d1-80b4-00c04fd430c8'
  },
  channel: ['sms']
});
response = client.messages.send(
    to=["+1234567890"],
    template={
        "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
    },
    channel=["sms"]
)
response, err := client.Messages.Send(context.Background(), sentdm.MessageSendParams{
    To: []string{"+1234567890"},
    Channel: []string{"sms"},
    Template: sentdm.MessageSendParamsTemplate{
        ID: sentdm.String("7ba7b820-9dad-11d1-80b4-00c04fd430c8"),
    },
})
MessageSendParams params = MessageSendParams.builder()
    .addTo("+1234567890")
    .addChannel("sms")
    .template(MessageSendParams.Template.builder()
        .id("7ba7b820-9dad-11d1-80b4-00c04fd430c8")
        .build())
    .build();

var response = client.messages().send(params);
MessageSendParams parameters = new()
{
    To = new List<string> { "+1234567890" },
    Channel = new List<string> { "sms" },
    Template = new MessageSendParamsTemplate
    {
        Id = "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
    }
};

var response = await client.Messages.Send(parameters);
$result = $client->messages->send(
    to: ['+1234567890'],
    template: [
        'id' => '7ba7b820-9dad-11d1-80b4-00c04fd430c8'
    ],
    channel: ['sms']
);
result = sent_dm.messages.send_(
  to: ["+1234567890"],
  template: {
    id: "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
  },
  channel: ["sms"]
)

Channel Values

channel valueBehavior
Omitted or ["sent"]Automatic selection with cross-channel fallback (the default).
["sms"]Pins every message to SMS.
["whatsapp"]Pins every message to WhatsApp. Fails for contacts not reachable on WhatsApp.
["rcs"]Pins every message to RCS. Fails when the recipient's device or carrier doesn't support RCS.
["mms"]Pins every message to MMS. Requires allowMms on the account, a template carrying an mms body, and an MMS-capable route for the recipient. See Sending MMS.
Multiple values, for example ["whatsapp", "sms", "rcs"]Broadcasts on every listed channel: a separate message per (recipient, channel) pair.

Any other value fails request validation with a 400 error. Refer to the Channel Routing reference for the full resolution and fallback rules.

A pinned message never falls back to another channel: if the pinned channel can't deliver to a recipient, that message fails. To reach each recipient over the best available channel, omit the channel field (automatic selection).

Broadcasting to Multiple Channels

The channel array is a broadcast list, not a fallback order. When you specify multiple channels, Sent creates a separate message for each (recipient, channel) pair and dispatches them all. Each message gets its own message_id and is billed independently: ["whatsapp", "sms", "rcs"] sends every recipient three messages.

Send From Specific Numbers

When your account holds several SMS numbers, channels chooses which of them a send may go out from. channels.sms is a list of entries, and each entry lists numbers in from:

curl -X POST "https://api.sent.dm/v3/messages" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["+12125551234", "+14155551234"],
    "template": {
      "id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8"
    },
    "channel": ["sms"],
    "channels": {
      "sms": [
        { "from": ["+12125550000", "+14155550000"] }
      ]
    }
  }'

How it works:

  • channel and channels combine. channel chooses the channel and channels chooses the numbers, so "channel": ["sms"] with an sms list sends on SMS from those numbers.
  • Routing picks among the listed numbers. Each recipient is matched against your routes the usual way, and routes pinned to a number you did not list are removed. The numbers from every entry in the list apply to every recipient.
  • A matching area code is preferred. When several of the listed numbers could serve a recipient equally well, the one whose area code matches the recipient's wins: in the example, the 212 number serves the 212 recipient and the 415 number serves the 415 recipient. A recipient in a third area code still gets the message.
  • country and strategy are optional. Each entry accepts them, for example { "country": "US", "from": [...] }. Both are stored with the message but do not change routing yet.

A request is rejected with a 400 error if channels names a key that is not a channel (including sent), names the same channel twice, holds an empty list, has an entry with an empty from, or has an entry in from that is not a valid E.164 number.

Every listed number must be an active SMS sender on your account. This is checked for each message when it is sent, not when the request arrives. The request is still accepted with 202, and each message that names an unusable number ends BLOCKED with reason_code BUSINESS_029, on GET /v3/messages/{id} and on the message.blocked webhook. The reason names the number by its position, for example channels.sms[0].from[1], never by the number itself. The whole message is blocked rather than sent from the remaining numbers. A blocked message is not charged and does not count against your deliverability rate.

The choice stays with the message. A message held for scheduled_at or quiet hours, a resend, and a reroute after a delivery failure all keep the numbers you listed, and a scheduled message is checked against your account as it stands when it is released. Sandbox mode does not check the numbers. See Choosing the sending number for the routing rules.

Response Overview

Success Response (202 Accepted)

{
  "success": true,
  "data": {
    "status": "QUEUED",
    "template_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
    "template_name": "order_confirmation",
    "recipients": [
      {
        "message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
        "to": "+14155551234",
        "channel": "sms"
      }
    ]
  },
  "error": null,
  "meta": {
    "request_id": "req_7X9zKp2jDw",
    "timestamp": "2026-03-04T11:28:25.2096416+00:00",
    "version": "v3"
  }
}

Important: Store the message_id from each recipient object. You'll need it to track delivery status.

Error Response (4xx/5xx)

{
  "success": false,
  "status": 402,
  "error": {
    "code": "BUSINESS_003",
    "message": "Account balance is insufficient to send this message",
    "doc_url": "https://docs.sent.dm/reference/api/error-catalog"
  },
  "meta": {
    "request_id": "req_7X9zKp2jDw",
    "timestamp": "2026-03-04T11:28:25.2096416+00:00",
    "version": "v3"
  }
}

Common error codes:

  • RESOURCE_002 - Template not found or not approved
  • BUSINESS_003 - Insufficient account balance
  • BUSINESS_002 - Rate limit exceeded (10,000 req/min standard, 10 req/min sensitive)
  • VALIDATION_001 - Request validation failed (for example, both or neither of text and template supplied)
  • VALIDATION_002 - Invalid phone number format
  • BUSINESS_005 - Template not approved for sending
  • BUSINESS_007 - Channel not available for this contact
  • BUSINESS_008 - Operation would exceed quota

These two are not returned on the response. The send is accepted with 202, and the code arrives as the message's reason_code on GET /v3/messages/{id} and on its message.failed or message.blocked webhook:

  • WHATSAPP_TEMPLATE_REQUIRED - Free-form WhatsApp message outside Meta's 24-hour customer-service window
  • CONVERSATION_TEMPLATE_REQUIRED - Free-form message with no open conversation on SMS, RCS, or auto-detect

Next Steps


On this page