Message Response Handling
Complete guide to understanding and handling API responses when sending messages.
Response Structure
All API responses follow a consistent structure:
{
"success": boolean,
"data": object | null,
"error": object | null,
"meta": {
"request_id": string,
"timestamp": string,
"version": "v3"
}
}| Field | Type | Description |
|---|---|---|
success | boolean | true for 2xx responses, false for errors |
data | object | null | Response payload (null on error) |
error | object | null | Error details (null on success) |
meta | object | Request metadata including request_id for support |
Success Response (202 Accepted)
When a message is successfully 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"
}
}const response = await client.messages.send({
to: ['+1234567890'],
template: { id: 'tmpl_123' }
});
// Access response data
console.log(response.data.status); // "QUEUED"
console.log(response.data.template_id); // Template UUID
console.log(response.data.template_name);
// Access recipient info
const recipient = response.data.recipients[0];
console.log(recipient.message_id); // Message UUID for tracking
console.log(recipient.to); // Phone number
console.log(recipient.channel); // "sms" or "whatsapp"
// Access metadata
console.log(response.meta.request_id); // For support inquiries
console.log(response.meta.timestamp); // Response timestampresponse = client.messages.send(
to=["+1234567890"],
template={"id": "tmpl_123"}
)
# Access response data
print(response.data.status) # "QUEUED"
print(response.data.template_id) # Template UUID
print(response.data.template_name)
# Access recipient info
recipient = response.data.recipients[0]
print(recipient.message_id) # Message UUID for tracking
print(recipient.to) # Phone number
print(recipient.channel) # "sms" or "whatsapp"
# Access metadata
print(response.meta.request_id) # For support inquiries
print(response.meta.timestamp) # Response timestampresponse, err := client.Messages.Send(context.Background(), params)
if err != nil {
log.Fatal(err)
}
// Access response data
fmt.Println(response.Data.Status) // "QUEUED"
fmt.Println(response.Data.TemplateID) // Template UUID
fmt.Println(response.Data.TemplateName)
// Access recipient info
recipient := response.Data.Recipients[0]
fmt.Println(recipient.MessageID) // Message UUID for tracking
fmt.Println(recipient.To) // Phone number
fmt.Println(recipient.Channel) // "sms" or "whatsapp"
// Access metadata
fmt.Println(response.Meta.RequestID) // For support inquiries
fmt.Println(response.Meta.Timestamp) // Response timestampvar response = client.messages().send(params);
// Access response data
System.out.println(response.data().status()); // "QUEUED"
System.out.println(response.data().templateId()); // Template UUID
System.out.println(response.data().templateName());
// Access recipient info
var recipient = response.data().recipients().get(0);
System.out.println(recipient.messageId()); // Message UUID for tracking
System.out.println(recipient.to()); // Phone number
System.out.println(recipient.channel()); // "sms" or "whatsapp"
// Access metadata
System.out.println(response.meta().requestId()); // For support inquiries
System.out.println(response.meta().timestamp()); // Response timestampvar response = await client.Messages.SendAsync(parameters);
// Access response data
Console.WriteLine(response.Data.Status); // "QUEUED"
Console.WriteLine(response.Data.TemplateId); // Template UUID
Console.WriteLine(response.Data.TemplateName);
// Access recipient info
var recipient = response.Data.Recipients[0];
Console.WriteLine(recipient.MessageId); // Message UUID for tracking
Console.WriteLine(recipient.To); // Phone number
Console.WriteLine(recipient.Channel); // "sms" or "whatsapp"
// Access metadata
Console.WriteLine(response.Meta.RequestId); // For support inquiries
Console.WriteLine(response.Meta.Timestamp); // Response timestamp$result = $client->messages->send(
to: ['+1234567890'],
template: ['id' => 'tmpl_123']
);
// Access response data
echo $result->data->status; // "QUEUED"
echo $result->data->template_id; // Template UUID
echo $result->data->template_name;
// Access recipient info
$recipient = $result->data->recipients[0];
echo $recipient->message_id; // Message UUID for tracking
echo $recipient->to; // Phone number
echo $recipient->channel; // "sms" or "whatsapp"
// Access metadata
echo $result->meta->request_id; // For support inquiries
echo $result->meta->timestamp; // Response timestampresult = sent_dm.messages.send(
to: ["+1234567890"],
template: { id: "tmpl_123" }
)
# Access response data
puts result.data.status # "QUEUED"
puts result.data.template_id # Template UUID
puts result.data.template_name
# Access recipient info
recipient = result.data.recipients[0]
puts recipient.message_id # Message UUID for tracking
puts recipient.to # Phone number
puts recipient.channel # "sms" or "whatsapp"
# Access metadata
puts result.meta.request_id # For support inquiries
puts result.meta.timestamp # Response timestampMulti-Channel Response
When sending to multiple channels, you get one recipient entry per (recipient, channel) pair:
{
"success": true,
"data": {
"status": "QUEUED",
"template_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
"template_name": "order_confirmation",
"recipients": [
{
"message_id": "msg-uuid-1",
"to": "+14155551234",
"channel": "whatsapp"
},
{
"message_id": "msg-uuid-2",
"to": "+14155551234",
"channel": "sms"
}
]
},
"error": null,
"meta": {
"request_id": "req_7X9zKp2jDw",
"timestamp": "2026-03-04T11:28:25.2096416+00:00",
"version": "v3"
}
}Error Responses
400 Bad Request (Validation Error)
{
"success": false,
"data": null,
"error": {
"code": "VALIDATION_004",
"message": "Request validation failed",
"details": {
"to": ["'to' must contain at least one recipient"],
"template": ["'template' is required"]
},
"doc_url": "https://docs.sent.dm/errors/VALIDATION_004"
},
"meta": {
"request_id": "req_7X9zKp2jDw",
"timestamp": "2026-03-04T11:28:25.2096416+00:00",
"version": "v3"
}
}import { ValidationError } from '@sentdm/sentdm';
try {
const response = await client.messages.send({
to: [], // Empty array - will fail validation
template: { id: 'tmpl_123' }
});
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.code); // "VALIDATION_004"
console.log(error.message); // "Request validation failed"
console.log(error.details); // { to: [...], template: [...] }
console.log(error.docUrl); // Documentation URL
}
}from sent_dm.errors import ValidationError
try:
response = client.messages.send(
to=[], # Empty list - will fail validation
template={"id": "tmpl_123"}
)
except ValidationError as e:
print(e.code) # "VALIDATION_004"
print(e.message) # "Request validation failed"
print(e.details) # {"to": [...], "template": [...]}
print(e.doc_url) # Documentation URLresponse, err := client.Messages.Send(context.Background(), params)
if err != nil {
if validationErr, ok := err.(*sentdm.ValidationError); ok {
fmt.Println(validationErr.Code) // "VALIDATION_004"
fmt.Println(validationErr.Message) // "Request validation failed"
fmt.Println(validationErr.Details) // Field-specific errors
fmt.Println(validationErr.DocURL) // Documentation URL
}
}try {
var response = client.messages().send(params);
} catch (ValidationException e) {
System.out.println(e.getCode()); // "VALIDATION_004"
System.out.println(e.getMessage()); // "Request validation failed"
System.out.println(e.getDetails()); // Field-specific errors
System.out.println(e.getDocUrl()); // Documentation URL
}try
{
var response = await client.Messages.SendAsync(parameters);
}
catch (ValidationException ex)
{
Console.WriteLine(ex.Code); // "VALIDATION_004"
Console.WriteLine(ex.Message); // "Request validation failed"
Console.WriteLine(ex.Details); // Field-specific errors
Console.WriteLine(ex.DocUrl); // Documentation URL
}use SentDM\Exceptions\ValidationException;
try {
$result = $client->messages->send(to: [], template: ['id' => 'tmpl_123']);
} catch (ValidationException $e) {
echo $e->getCode(); // "VALIDATION_004"
echo $e->getMessage(); // "Request validation failed"
print_r($e->getDetails()); // Field-specific errors
echo $e->getDocUrl(); // Documentation URL
}begin
result = sent_dm.messages.send(to: [], template: { id: "tmpl_123" })
rescue Sentdm::ValidationError => e
puts e.code # "VALIDATION_004"
puts e.message # "Request validation failed"
puts e.details # Field-specific errors
puts e.doc_url # Documentation URL
end401 Unauthorized
Invalid or missing API key:
{
"success": false,
"data": null,
"error": {
"code": "AUTH_001",
"message": "Invalid API key",
"details": null,
"doc_url": "https://docs.sent.dm/errors/AUTH_001"
}
}404 Not Found
Template not found:
{
"success": false,
"data": null,
"error": {
"code": "RESOURCE_002",
"message": "Template not found",
"details": null,
"doc_url": "https://docs.sent.dm/errors/RESOURCE_002"
}
}429 Rate Limit
Too many requests:
{
"success": false,
"data": null,
"error": {
"code": "BUSINESS_002",
"message": "Rate limit exceeded. Retry after 60 seconds",
"details": {
"retry_after": 60,
"limit": 200,
"window": "60s"
},
"doc_url": "https://docs.sent.dm/errors/BUSINESS_002"
}
}402 Payment Required
Insufficient balance:
{
"success": false,
"data": null,
"error": {
"code": "BUSINESS_003",
"message": "Account balance is insufficient to send this message",
"details": {
"balance": 0.50,
"required": 0.75
},
"doc_url": "https://docs.sent.dm/errors/BUSINESS_003"
}
}Error Code Reference
| Code | HTTP | Description | Action |
|---|---|---|---|
VALIDATION_002 | 400 | Invalid phone number format | Check E.164 format |
VALIDATION_004 | 400 | Missing required fields | Check request body |
AUTH_001 | 401 | Invalid API key | Verify API key |
RESOURCE_002 | 404 | Template not found | Check template ID |
RESOURCE_003 | 404 | Contact not found | Check contact ID |
BUSINESS_002 | 429 | Rate limit exceeded | Implement backoff |
BUSINESS_003 | 402 | Insufficient balance | Add credits |
BUSINESS_005 | 400 | Template not approved | Check template status |
INTERNAL_001 | 500 | Server error | Retry with backoff |
Handling Errors in Production
import { SentDm, ValidationError, RateLimitError, BusinessError } from '@sentdm/sentdm';
const client = new SentDm();
async function sendWithRetry(params: MessageSendParams, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await client.messages.send(params);
} catch (error) {
// Don't retry validation errors
if (error instanceof ValidationError) {
throw error;
}
// Handle rate limits with exponential backoff
if (error instanceof RateLimitError) {
const delay = Math.pow(2, attempt) * 1000;
await sleep(delay);
continue;
}
// Handle insufficient balance
if (error instanceof BusinessError && error.code === 'BUSINESS_003') {
await alertBillingTeam();
throw error;
}
// Last attempt - throw error
if (attempt === maxRetries) {
throw error;
}
}
}
}import time
from sent_dm import SentDm
from sent_dm.errors import ValidationError, RateLimitError, BusinessError
client = SentDm()
def send_with_retry(params, max_retries=3):
for attempt in range(1, max_retries + 1):
try:
return client.messages.send(**params)
except ValidationError:
# Don't retry validation errors
raise
except RateLimitError as e:
# Handle rate limits with exponential backoff
if attempt < max_retries:
delay = 2 ** attempt
time.sleep(delay)
continue
raise
except BusinessError as e:
if e.code == 'BUSINESS_003':
alert_billing_team()
raiseimport (
"context"
"fmt"
"time"
"github.com/sentdm/sent-dm-go"
)
func sendWithRetry(ctx context.Context, client *sentdm.Client, params sentdm.MessageSendParams, maxRetries int) (*sentdm.MessageResponse, error) {
for attempt := 1; attempt <= maxRetries; attempt++ {
response, err := client.Messages.Send(ctx, params)
if err == nil {
return response, nil
}
// Don't retry validation errors
if _, ok := err.(*sentdm.ValidationError); ok {
return nil, err
}
// Handle rate limits with exponential backoff
if _, ok := err.(*sentdm.RateLimitError); ok {
if attempt < maxRetries {
delay := time.Duration(1<<attempt) * time.Second
time.Sleep(delay)
continue
}
}
// Handle insufficient balance
if businessErr, ok := err.(*sentdm.BusinessError); ok && businessErr.Code == "BUSINESS_003" {
alertBillingTeam()
}
return nil, err
}
return nil, fmt.Errorf("max retries exceeded")
}import dm.sent.client.SentDmClient;
import dm.sent.errors.*;
public class MessageSender {
private final SentDmClient client;
public MessageSendResponse sendWithRetry(MessageSendParams params, int maxRetries) {
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return client.messages().send(params);
} catch (ValidationException e) {
// Don't retry validation errors
throw e;
} catch (RateLimitException e) {
// Handle rate limits with exponential backoff
if (attempt < maxRetries) {
try {
Thread.sleep((long) Math.pow(2, attempt) * 1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException(ie);
}
continue;
}
throw e;
} catch (BusinessException e) {
if ("BUSINESS_003".equals(e.getCode())) {
alertBillingTeam();
}
throw e;
}
}
throw new RuntimeException("Max retries exceeded");
}
}using Sentdm;
using Sentdm.Errors;
public class MessageSender
{
private readonly SentDmClient _client;
public async Task<MessageResponse> SendWithRetryAsync(
MessageSendParams parameters,
int maxRetries = 3)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
return await _client.Messages.SendAsync(parameters);
}
catch (ValidationException)
{
// Don't retry validation errors
throw;
}
catch (RateLimitException)
{
// Handle rate limits with exponential backoff
if (attempt < maxRetries)
{
var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt));
await Task.Delay(delay);
continue;
}
throw;
}
catch (BusinessException ex) when (ex.Code == "BUSINESS_003")
{
await AlertBillingTeamAsync();
throw;
}
}
throw new Exception("Max retries exceeded");
}
}use SentDM\Client;
use SentDM\Exceptions\ValidationException;
use SentDM\Exceptions\RateLimitException;
use SentDM\Exceptions\BusinessException;
class MessageSender
{
private Client $client;
public function sendWithRetry(array $params, int $maxRetries = 3)
{
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
return $this->client->messages->send(...$params);
} catch (ValidationException $e) {
// Don't retry validation errors
throw $e;
} catch (RateLimitException $e) {
// Handle rate limits with exponential backoff
if ($attempt < $maxRetries) {
sleep(2 ** $attempt);
continue;
}
throw $e;
} catch (BusinessException $e) {
if ($e->getCode() === 'BUSINESS_003') {
$this->alertBillingTeam();
}
throw $e;
}
}
throw new Exception('Max retries exceeded');
}
}class MessageSender
def initialize(client)
@client = client
end
def send_with_retry(params, max_retries: 3)
max_retries.times do |attempt|
begin
return @client.messages.send(**params)
rescue Sentdm::ValidationError
# Don't retry validation errors
raise
rescue Sentdm::RateLimitError
# Handle rate limits with exponential backoff
if attempt < max_retries - 1
sleep(2 ** (attempt + 1))
retry
end
raise
rescue Sentdm::BusinessError => e
if e.code == 'BUSINESS_003'
alert_billing_team
end
raise
end
end
raise 'Max retries exceeded'
end
endRelated Guides
Sending Messages
Core message sending guide with all SDK examples
Best Practices
Sandbox mode, idempotency, and cost optimization
Error Handling
Comprehensive error handling patterns