Send
Deliver Email, WhatsApp, or SMS directly, or let smart routing choose between WhatsApp and SMS.
Send transactional messages, keep customer records in sync, inspect campaign results, and react to delivery events from one API.
Copy an accurate, compact brief with the endpoints, rules, and retry behaviour it needs.
# Convert integration brief for AI agents
Convert is a multi-channel customer messaging platform. Its public REST API sends one transactional message at a time over Email, WhatsApp, SMS, or smart routing; creates and updates contacts; reads campaign results; manages SMS Sender ID requests; and emits signed webhooks.
Base URL: https://convert.ruut.chat/api/v1
Format: JSON
Authentication: Authorization: Bearer <rk_live_...>
API keys are organisation-scoped, shown once, revocable, and can expire.
Required scopes:
- messages:write — POST /messages
- contacts:write — POST, PATCH, DELETE /contacts
- campaigns:read — GET /campaigns and /campaigns/:id
- sender_ids:read — GET /sender_id_requests and /sender_id_requests/:id
- sender_ids:write — POST /sender_id_requests
Transactional messages:
POST /messages requires Content-Type: application/json and a unique Idempotency-Key header (maximum 200 characters). Reuse a key only to retry the identical body. The same key with a different body returns 409 idempotency_conflict.
Message fields:
- to: email address for email; E.164 phone number for WhatsApp/SMS
- channel: email | whatsapp | sms | smart
- message: plain-text content; required unless template is supplied
- template: saved template public ID such as tpl_x83k29sk
- variables: object used to fill a template's required merge fields
- subject: required for raw email unless supplied by the template
- html: optional HTML body for raw email; message remains the plain-text alternative
- sender: optional active email sender address; otherwise the default active sender is used
- test: true validates without sending or charging
Channel behavior:
- email sends through the connected, confirmed email sender and respects suppression/unsubscribe state
- whatsapp requires a connected WhatsApp account; compatible connected accounts are attempted in the workspace's primary-first order
- sms requires an approved Sender ID
- smart uses WhatsApp when available and eligible, otherwise SMS
Contacts:
- POST /contacts creates or enriches by normalized email, E.164 phone, or external_customer_id
- PATCH /contacts/:id partially updates an organisation-owned contact
- DELETE /contacts/:id permanently deletes it and returns 204
- accepted fields include phone_number, email, first_name, last_name, company, external_customer_id, opted_out, tags[], and custom_fields{}
Campaigns:
- GET /campaigns returns recent campaigns and delivery totals
- GET /campaigns/:id returns one campaign and its contact list
- campaign creation and sending are dashboard workflows; the public API is read-only for campaigns
SMS Sender IDs:
- GET /sender_id_requests and GET /sender_id_requests/:id read registration status
- POST /sender_id_requests submits requested_sender_id plus optional sms_type, carrier(s), use_case, and transactional/promotional values
Responses and retries:
- successful message sends return 201 with success, channel, status, message_id, cost, and to
- errors normally return success: false, error, and stable error_code
- retry network failures and 5xx responses with backoff using the same idempotency key and identical body
- do not retry validation 4xx responses until the request or account setup is corrected
Webhooks:
Configure endpoints in Convert. Events include message.sent, message.delivered, message.failed, message.bounced, message.unsubscribed, message.opened, message.clicked, campaign.completed, and contact.created. Verify X-Convert-Signature by comparing it with "sha256=" + HMAC-SHA256(endpoint_secret, raw_request_body). Return any 2xx response. Failed deliveries are retried up to five attempts with backoff.
Integration rule: treat message_id as acceptance by the delivery channel, not proof of delivery. Use delivery webhooks for final state.
Convert gives your application one place to handle the practical parts of customer messaging. You can send an OTP or order update, maintain a customer profile, read campaign performance, register an SMS Sender ID, and receive delivery updates without integrating separately with every channel.
Deliver Email, WhatsApp, or SMS directly, or let smart routing choose between WhatsApp and SMS.
Use saved templates, merge variables, and customer fields for consistent transactional messages.
Read campaign totals and consume signed webhooks for accepted, delivered, failed, and engagement events.
https://convert.ruut.chat/api/v1
There are four things to do before your application sends its first live message.
Connect Email or WhatsApp in Channels. For SMS, complete Sender ID approval first.
Open Settings → Developer → API keys. Copy the key immediately because it is shown only once.
Use a real recipient you control and a new idempotency key. Add "test": true to validate without delivery or a wallet charge.
Add a webhook endpoint so your system can move from “accepted” to the final delivery state.
curl -X POST https://convert.ruut.chat/api/v1/messages \
-H "Authorization: Bearer rk_live_xxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"to": "+2348012345678",
"channel": "smart",
"message": "Hi Ada, your order is ready."
}'
A successful 201 Created means the selected channel accepted the message. It does not mean the recipient has received it. Use webhooks for delivery confirmation.
Convert validates the request and account setup before it creates a customer record or charges the wallet.
The Bearer key identifies the organisation and permitted scopes.
Recipient, channel, template variables, sender setup, and idempotency are checked.
Convert resolves the requested channel and attempts the connected account in primary-first order.
The wallet charge is settled only after a billable message is accepted.
Later delivery and engagement events arrive asynchronously through webhooks.
Create a key under API keys in Settings → Developer. A key belongs to one organisation, can expire or be revoked, and is shown in full only once. Store it in a server-side secret manager—never in browser code, a mobile application, or source control.
Authorization: Bearer rk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Requests without a valid active key return 401 Unauthorized. A valid key without the scope required by an endpoint returns 403 Forbidden.
| Scope | Allows |
|---|---|
messages:write | Send and validate transactional messages. |
contacts:write | Create, enrich, update, and delete contacts. |
campaigns:read | List campaigns and read campaign details. |
sender_ids:read | Read SMS Sender ID requests and their approval state. |
sender_ids:write | Submit a new SMS Sender ID request. |
If a key is exposed, revoke it in the dashboard and create a replacement. Convert stores only a digest and cannot show the original key again.
POST /api/v1/messages
Send a single transactional message (OTP, alert, notification). The channel decides how it's delivered.
Idempotency-Key is required.
Generate one unique value for every new message. Reuse the same value only when retrying the exact same request after a timeout; a changed OTP, recipient, template, variables, or channel must use a new value.
| Field | Type | Description |
|---|---|---|
to | string | Email address for email; E.164 phone number for WhatsApp/SMS, e.g. +2348012345678. Required. |
message | string | The text to send. Required unless a template is supplied. |
template | string | A template ID (e.g. tpl_x83k29sk). Renders your saved design — see Templates. |
variables | object | Only the caller-facing values shown by the template, such as otp or meta_1. Convert builds provider-specific Meta components and copy-code buttons. |
channel | string | email, whatsapp, sms, or smart. Defaults to smart whenever omitted, including template sends. |
subject | string | Required for raw Email sends; an Email template can supply it. |
html | string | Optional HTML body for a raw Email send. message remains the plain-text alternative. |
sender | string | Optional active Email sender address. The default active sender is used when omitted. |
test | boolean | When true, validates channel, recipient, template, and required options without sending or charging. |
first_name | string | Optional — stored on the contact, usable in {{first_name}}. |
last_name | string | Optional. |
list_id | integer | Optional — saves or updates the recipient in that workspace contact list. When omitted, Convert uses API Contacts. |
custom_fields | object | Optional profile attributes to save on the contact. Template variables such as OTPs are never stored automatically. |
session | string | Optional WhatsApp session id (defaults to your connected number). |
Sends from an active, confirmed sender configured in Convert. Addresses that previously hard-bounced, complained, or unsubscribed are suppressed before submission.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST https://convert.ruut.chat/api/v1/messages \
-H "Authorization: Bearer rk_live_xxx" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "[email protected]",
"channel": "email",
"subject": "Your order is on its way",
"sender": "[email protected]",
"message": "Hi {{first_name}}, order ORD-1042 has shipped.",
"html": "<p>Hi {{first_name}}, order <strong>ORD-1042</strong> has shipped.</p>",
"first_name": "Ada"
}'
Delivers strictly over WhatsApp. Fails if no number is connected, or the recipient isn't reachable on WhatsApp.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST https://convert.ruut.chat/api/v1/messages \
-H "Authorization: Bearer rk_live_xxx" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+2348012345678",
"message": "Your code is 123456",
"channel": "whatsapp"
}'
Sends over SMS using your approved transactional Sender ID.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST https://convert.ruut.chat/api/v1/messages \
-H "Authorization: Bearer rk_live_xxx" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+2348012345678",
"message": "Your code is 123456",
"channel": "sms"
}'
The default. Sends over WhatsApp when a number is connected and the recipient is on WhatsApp; otherwise selects SMS. SMS still requires an approved Sender ID and enough wallet balance.
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST https://convert.ruut.chat/api/v1/messages \
-H "Authorization: Bearer rk_live_xxx" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+2348012345678",
"message": "Hi {{first_name}}, your order shipped!",
"channel": "smart",
"first_name": "Ada"
}'
201 Created{
"success": true,
"channel": "whatsapp",
"status": "sent",
"message_id": "wamid.XXXX",
"cost": 5.0,
"to": "+2348012345678"
}
Add "test": true to validate a request without delivering it, creating a recipient status record, or charging the wallet. The response status is validated and cost is zero.
{
"to": "[email protected]",
"channel": "email",
"subject": "Your order is on its way",
"message": "Hi Ada, order ORD-1042 has shipped.",
"test": true
}
POST /api/v1/messages/batch takes up to
1,000 recipients in one request, each with
their own message. Use it instead of looping over /api/v1/messages: one request per recipient sends
everything at the provider simultaneously, which is what earns rate-limit errors on a list of any size.
Convert queues the batch and releases it at the safe rate for whichever provider is carrying it — fast for Meta Cloud WhatsApp, deliberately slow and jittered for a connected Convert WhatsApp number, where bursts risk the number itself. Eighty messages are eighty separate sends, spaced out; you make one call.
These apply to the batch as a whole.
| Field | Type | Description |
|---|---|---|
messages | array | Required. One object per recipient — see the table below. |
channel | string | whatsapp, sms, email or smart. One channel for the whole batch. Defaults to smart. |
template | string | Optional template ID, rendered separately for each recipient using that recipient's variables. |
sender | string | Active Email sender address. Email channel only; the default active sender is used when omitted. |
list_id | integer | Contact list new contacts are filed under. Defaults to API Contacts. |
messagesOne recipient, and what to send them.
| Field | Type | Description |
|---|---|---|
to | string | Required. E.164 phone number, or an email address on the email channel. |
message | string | This recipient's own text. Required unless a template is given. Supports {{merge_fields}}. |
variables | object | This recipient's template variables. Also fills {{merge_fields}} in a raw message. |
subject | string | Required per recipient on the email channel, unless the template supplies one. |
media_url | string | Image or document to send with the message. WhatsApp only. |
reference | string | Your own id for this recipient — an order number, a job id. Echoed back on the status endpoint so you can match outcomes to your records without storing ours. Must be unique within the batch. |
Optional, and saved to the contact record rather than used for this one send — so a recipient you reach through the API is a real contact afterwards, usable in campaigns and personalisation. Skip them if you already manage contacts through the contacts API.
| Field | Type | Description |
|---|---|---|
first_name | string | Stored on the contact and available as {{first_name}}. Equivalent to putting first_name in variables — either works, no need for both. |
last_name | string | As above, for {{last_name}}. |
custom_fields | object | Merged into the contact's custom attributes, and usable as merge fields in later sends. |
whatsapp_opt_in | boolean | Records that this recipient consented to WhatsApp. Required by Meta Cloud before you can message them. |
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST https://convert.ruut.chat/api/v1/messages/batch \
-H "Authorization: Bearer rk_live_xxx" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"messages": [
{ "to": "+2348012345678", "message": "Hi Ada, order #1201 shipped.", "reference": "order-1201" },
{ "to": "+2348098765432", "message": "Hi Tobi, order #1202 ships tomorrow.", "reference": "order-1202" },
{ "to": "+2348055500011", "message": "Hi Nkem, order #1203 is ready for pickup.", "reference": "order-1203" }
]
}'
202 Accepted
202 means queued, not sent. Validation, contact creation and the wallet reservation are all done
by the time you get this — so the accepted list is a firm commitment — but no message has reached the provider yet.
{
"success": true,
"batch_id": "batch_9f2c7a1de0b34c85a6d1",
"status": "queued",
"channel": "whatsapp",
"queued": 3,
"rejected_count": 0,
"counts": { "pending": 3, "sent": 0, "failed": 0, "skipped": 0 },
"reserved_cost": 13.5,
"accepted": [
{ "index": 0, "to": "+2348012345678", "reference": "order-1201", "channel": "whatsapp", "message_id": 90211, "cost": 4.5 },
{ "index": 1, "to": "+2348098765432", "reference": "order-1202", "channel": "whatsapp", "message_id": 90212, "cost": 4.5 },
{ "index": 2, "to": "+2348055500011", "reference": "order-1203", "channel": "whatsapp", "message_id": 90213, "cost": 4.5 }
],
"rejected": []
}
One bad row does not sink the batch. Valid recipients queue, and each rejection comes back with the index
of the entry in the array you sent, so you can fix and resubmit just those.
{
"success": true,
"batch_id": "batch_4b81ea22c7d94f60b3aa",
"queued": 1,
"rejected_count": 2,
"rejected": [
{ "index": 1, "to": "not-a-phone", "error": "'not-a-phone' is not a valid phone number.", "error_code": "invalid_phone" },
{ "index": 2, "to": "+2348055500011", "error": "'message' is required when no template is given.", "error_code": "missing_message" }
]
}
GET /api/v1/messages/batch/:batch_id returns the running counts and every recipient's outcome.
Delivery also fires the usual message.* webhooks,
which is the better option if you would otherwise poll.
{
"success": true,
"batch_id": "batch_9f2c7a1de0b34c85a6d1",
"status": "partially_failed",
"counts": { "pending": 0, "sent": 2, "failed": 1, "skipped": 0 },
"messages": [
{ "message_id": 90211, "reference": "order-1201", "to": "+2348012345678", "status": "delivered",
"channel": "whatsapp", "provider_message_id": "wamid.XXXX", "cost": 4.5 },
{ "message_id": 90213, "reference": "order-1203", "to": "+2348055500011", "status": "failed",
"channel": "whatsapp", "error": "not_on_whatsapp", "cost": 0.0 }
]
}
The whole batch is reserved against your wallet when it is accepted, and each message is captured when it sends or
refunded when it fails. If your balance runs out partway through, the remaining recipients are rejected with
insufficient_balance rather than silently dropped. One wallet entry covers the batch.
Design a message once in Templates —
choose how it looks, add {{variables}}, and pick a type (transactional or promotional). Each template gets an
immutable ID like tpl_x83k29sk. Send it by passing that template id plus a variables object —
Convert renders your saved design and uses smart routing unless you explicitly choose a channel. Provider-specific Meta parameters are generated behind the scenes; API callers supply only the variables displayed by Convert. Email templates also store a subject, preview text, from name, and reply-to address.
Open any template and click Use via API to copy its ID and ready-made snippets (cURL, Node, Python, PHP, Flutter, Go).
IDEMPOTENCY_KEY="$(uuidgen)"
curl -X POST https://convert.ruut.chat/api/v1/messages \
-H "Authorization: Bearer rk_live_xxx" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+2348012345678",
"template": "tpl_x83k29sk",
"channel": "smart",
"variables": {
"first_name": "Ada",
"otp": "638492",
"expiry": "10",
"company_name": "Acme"
}
}'
201 CreatedThe response echoes the template id that was sent.
{
"success": true,
"channel": "whatsapp",
"status": "sent",
"message_id": "wamid.XXXX",
"cost": 5.0,
"template": "tpl_x83k29sk",
"to": "+2348012345678"
}
Caller-facing variables marked required must be supplied (and non-empty). Optional ones may be omitted and fall back to their
configured default. Meta component aliases such as authentication button parameters are resolved by Convert and never appear as API requirements. Missing caller-facing variables return 422 with the list of what's missing:
{
"success": false,
"error": "Missing required variable(s): otp.",
"error_code": "missing_variables",
"missing": ["otp"]
}
Manage the people you message. Add them one at a time over the API, in bulk from a file, or let the send API create them automatically.
POST /api/v1/contacts
curl -X POST https://convert.ruut.chat/api/v1/contacts \
-H "Authorization: Bearer rk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+2348012345678",
"first_name": "Ada",
"last_name": "Obi",
"email": "[email protected]"
}'
Phone numbers are normalized to E.164 and emails to lowercase. A matching normalized phone, email, or external customer ID enriches the existing organisation contact instead of creating a duplicate.
PATCH /api/v1/contacts/:id
Replace :id with the numeric contact ID. This is a partial update: omitted fields keep their existing values. The API key can update only contacts in its organisation.
curl -X PATCH https://convert.ruut.chat/api/v1/contacts/1042 \
-H "Authorization: Bearer rk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Adanna",
"email": "[email protected]",
"tags": ["customer", "vip"],
"custom_fields": { "tier": "platinum" }
}'
Returns 200 OK, 404 Not Found for an unknown or cross-organisation ID, or 422 Unprocessable Entity for invalid or duplicate identifiers.
DELETE /api/v1/contacts/:id
Permanently removes the contact from the API key's organisation.
curl -X DELETE https://convert.ruut.chat/api/v1/contacts/1042 \
-H "Authorization: Bearer rk_live_xxx"
Returns 204 No Content with no response body. Deletion also removes dependent recipient delivery records; use an opt-out when the customer and history should remain.
For large lists, import a CSV or Excel (.xlsx) file from Contacts → a list → Add contacts. You map your columns to fields (phone, first name, last name, email, and any custom fields) and Convert imports them in the background, checking each number against WhatsApp.
To bulk-load programmatically, loop the single-contact endpoint:
const contacts = [
{ phone_number: "+2348010000001", first_name: "Ada" },
{ phone_number: "+2348010000002", first_name: "Uche" }
];
for (const c of contacts) {
await fetch("https://convert.ruut.chat/api/v1/contacts", {
method: "POST",
headers: {
"Authorization": "Bearer rk_live_xxx",
"Content-Type": "application/json"
},
body: JSON.stringify(c)
});
}
Attach your own attributes via custom_fields. These become available as merge tags (e.g. {{order_id}}) in messages.
{
"phone_number": "+2348012345678",
"first_name": "Ada",
"custom_fields": {
"order_id": "ORD-1042",
"tier": "gold"
}
}
Campaign endpoints are read-only. Use them to bring broadcast names, channels, status, delivery totals, and cost into your own reporting. Campaign creation, editing, scheduling, resending, and cancellation remain dashboard workflows.
GET /api/v1/campaigns — list recent campaigns
GET /api/v1/campaigns/:id — one campaign with its list
curl https://convert.ruut.chat/api/v1/campaigns \
-H "Authorization: Bearer rk_live_xxx"
{
"id": 12,
"name": "October promo",
"status": "sent",
"routing_strategy": "smart_fallback",
"sent_count": 940,
"failed_count": 6,
"actual_cost": 4700.0,
"contact_list": { "id": 3, "name": "Customers" }
}
sent_count records accepted submissions, while delivered_count is updated when delivery receipts arrive.
Costs and counts can therefore continue to settle after a campaign first enters its final send state.
A Sender ID is the name recipients see for an SMS. It must be approved before Convert can send SMS for your organisation. These endpoints let an internal system submit a request and track its registration state.
/api/v1/sender_id_requestsList requests. Add ?status=pending or another known status to filter.
/api/v1/sender_id_requests/:idRead one request, including carrier-level approval and rejection states.
/api/v1/sender_id_requestsSubmit a new registration request.
curl -X POST https://convert.ruut.chat/api/v1/sender_id_requests \
-H "Authorization: Bearer rk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"requested_sender_id": "ACME",
"sms_type": "transactional",
"use_case": "Order and delivery notifications"
}'
requested_sender_id is required. You may also send carrier or carriers, sms_type,
transactional_sender_id, promotional_sender_id, and use_case. Approval is asynchronous;
poll the read endpoint or review the request in the dashboard before attempting SMS.
Campaigns route every contact using one of these strategies (the send API mirrors them via channel):
| Strategy | Behaviour |
|---|---|
whatsapp_only | WhatsApp only; contacts not on WhatsApp are skipped. |
email_only | Email to recipients with a valid, non-suppressed address. |
sms_only | SMS to everyone via your Sender ID. |
whatsapp_first | Try WhatsApp; fall back to SMS for the rest. |
smart_fallback | Routes eligible, opted-in contacts to WhatsApp and the remaining active contacts to SMS. It does not retry failed WhatsApp submissions over SMS. |
Message state progresses from sent (provider accepted) to delivered when a delivery event arrives.
Email can also become bounced or unsubscribed. Campaign Email adds first-open and first-click timestamps;
opens can be under-counted by client privacy controls and prefetching can inflate them.
Get notified in real time when things happen. Add an endpoint under
Webhooks and pick the events you want.
Convert sends a POST with a JSON body for each event.
| Event | Fires when… |
|---|---|
message.sent | A single message was accepted by the provider. |
message.received | An inbound message was received on a connected WhatsApp channel. |
message.delivered | A message was delivered to the recipient's device. |
message.read | A WhatsApp message was read by the recipient. |
message.failed | A message could not be delivered. |
message.bounced | An email provider reported a hard or soft bounce. |
message.unsubscribed | An email recipient unsubscribed. |
message.opened | An email was opened (subject to client privacy behavior). |
message.clicked | A tracked link in an email was clicked. |
campaign.completed | A campaign finished sending to its whole list. |
contact.created | A new contact was added (via UI, import, or API). |
{
"event": "message.delivered",
"created_at": "2026-06-29T12:00:00Z",
"data": {
"message_id": 8841,
"to": "+2348012345678",
"channel": "whatsapp",
"status": "delivered",
"provider_message_id": "wamid.XXXX",
"cost": 5.0
}
}
Each request carries X-Convert-Signature: sha256=<hmac> — an HMAC-SHA256 of the raw body using your endpoint's
signing secret (shown on the endpoint page). Recompute and compare to confirm authenticity:
# Rails example
expected = "sha256=" + OpenSSL::HMAC.hexdigest(
"SHA256", ENV["CONVERT_WEBHOOK_SECRET"], request.raw_post
)
verified = ActiveSupport::SecurityUtils.secure_compare(
expected, request.headers["X-Convert-Signature"].to_s
)
Respond with any 2xx to acknowledge. Non-2xx (or timeouts) are retried with backoff.
Failed requests return a JSON body with error and a stable error_code:
{
"success": false,
"error": "Insufficient wallet balance to send this message.",
"error_code": "insufficient_balance"
}
| HTTP | error_code | Meaning |
|---|---|---|
| 400 | idempotency_key_required | Idempotency-Key is missing or longer than 200 characters. |
| 401 | unauthorized | Missing, invalid, or revoked API key. |
| 403 | forbidden | The API key does not include the required scope. |
| 402 | insufficient_balance | Wallet balance too low to send. |
| 409 | idempotency_conflict | The key was already used for a different request. |
| 409 | idempotency_in_progress | A request using this key is still being processed. |
| 422 | invalid_channel | channel must be email, whatsapp, sms, or smart. |
| 422 | invalid_phone | The to number isn't a valid phone number. |
| 422 | invalid_email | The Email to value isn't a valid address. |
| 422 | missing_subject | An Email subject was not supplied by the request or template. |
| 422 | email_sender_required | No active, confirmed Email sender is available. |
| 422 | email_sender_not_found | The requested sender is not active in this organisation. |
| 422 | email_suppressed | The recipient previously bounced, complained, or unsubscribed. |
| 422 | missing_message | No message body or template provided. |
| 404 | template_not_found | No template with that id in your account. |
| 422 | template_archived | The template is archived and can't be sent. |
| 422 | missing_variables | One or more required template variables were not supplied. |
| 422 | whatsapp_not_connected | WhatsApp channel requested but no number is connected. |
| 422 | recipient_not_on_whatsapp | Recipient isn't reachable on WhatsApp (use sms/smart). |
| 422 | unknown_session | The requested WhatsApp session does not belong to the organisation. |
| 422 | whatsapp_failed | No compatible connected WhatsApp account accepted the message. |
| 422 | sender_id_required | SMS was selected, but the account has no approved Sender ID. |
| 502 | send_failed / sms_failed / email_failed | The provider rejected or failed to send. |
Use this final pass before moving an integration from a local test to live customer traffic.