Messaging API
Send email, SMS and WhatsApp through your own provider accounts, read the delivery log and manage templates and suppressions.
Messaging sends email, SMS and WhatsApp messages through the provider accounts your workspace has connected (for example SendGrid, Twilio or WhatsApp Cloud). AxisIQ does not send for you: the sending, billing and reputation are your provider's. This API sends messages, reads the delivery log, and manages templates and the do-not-contact list.
Connecting a provider account involves secrets, so it is done in the app (Messaging › Channels). The secrets are never returned by the API.
If the Messaging app is switched off in Settings › Apps, these routes answer 404 NOT_FOUND.
Endpoints
Connections and messages are checked against the connection (its key is the resource), so a role can be limited to one mailbox or number. Templates and the do-not-contact list are workspace-wide: they need comms:read or comms:manage on all connections (*).
| Method | Path | Permission | What it does |
|---|---|---|---|
GET |
/orgs/{orgId}/comms/connections |
comms:read |
List connections |
GET |
/orgs/{orgId}/comms/connections/{connId} |
comms:read |
Get a connection |
GET |
/orgs/{orgId}/comms/providers |
comms:read |
Supported providers and the fields each needs |
GET |
/orgs/{orgId}/comms/connections/{connId}/templates |
comms:manage |
Read the provider's own approved templates |
POST |
/orgs/{orgId}/comms/messages |
comms:send |
Send a message |
GET |
/orgs/{orgId}/comms/messages |
comms:read |
The delivery log |
GET |
/orgs/{orgId}/comms/messages/{msgId} |
comms:read |
One message |
GET |
/orgs/{orgId}/comms/templates |
comms:read |
List templates (?channel=) |
GET |
/orgs/{orgId}/comms/templates/{tmplId} |
comms:read |
Get a template |
POST · PUT · DELETE |
/orgs/{orgId}/comms/templates[/{tmplId}] |
comms:manage |
Manage templates |
GET |
/orgs/{orgId}/comms/suppressions |
comms:read |
The do-not-contact list |
POST |
/orgs/{orgId}/comms/suppressions |
comms:manage |
Add an address |
DELETE |
/orgs/{orgId}/comms/suppressions/{supId} |
comms:manage |
Remove an address |
Connections can also be created (POST, comms:connect), edited (PUT), tested (POST …/{connId}/test) and deleted (DELETE, answers 204) with comms:manage. List endpoints return a plain array in data.
Connections
{
"id": "0190fa00-…",
"channel": "email",
"provider": "sendgrid",
"provider_label": "SendGrid",
"name": "Billing mail",
"key": "billing-mail",
"from_address": "billing@example.com",
"from_name": "Example Billing",
"enabled": true,
"status": "ok"
}
channel is email, sms or whatsapp. A connection also carries its non-secret config, capabilities, last_checked_at and last_error. Use id when sending through the REST API; the Functions SDK addresses connections by key.
POST /orgs/{orgId}/comms/messages
Queue one message. Permission: comms:send on the connection.
| Field | Type | Required | Notes |
|---|---|---|---|
connection_id |
string | Yes | The connection to send through |
to |
string | One of to or record_id |
An email address or phone number |
record_type, record_id, to_field |
string | Take the recipient from a field of a record, and fill the template from it. You need to be able to read the record | |
template_id |
string | One of a template or a body | A saved template. Its channel must match the connection's |
subject, body_text, body_html |
string | A message written inline instead of using a template | |
data |
object | No | Extra values for the template |
vars |
array of strings | No | Ordered values for providers whose templates take positional variables (WhatsApp) |
curl -s -X POST https://axisiq.co/api/v1/orgs/$ORG/comms/messages \
-H "Authorization: Bearer $AXIS_KEY" -H "Content-Type: application/json" \
-d '{"connection_id": "0190fa00-…", "to": "asha@example.com", "subject": "Your invoice", "body_text": "Hi Asha, invoice INV-1001 is ready."}'
Response 202 Accepted. The message is queued and sent in the background, so the call never waits on the provider.
{
"data": {
"id": "0190fa10-…",
"connection_id": "0190fa00-…",
"channel": "email",
"provider": "sendgrid",
"direction": "outbound",
"to": "asha@example.com",
"from": "billing@example.com",
"subject": "Your invoice",
"status": "queued",
"created_at": "2026-10-04T09:30:00Z"
}
}
Message statuses
| Status | Meaning |
|---|---|
queued |
Accepted, waiting to be sent |
sending |
Being handed to the provider |
sent |
The provider accepted it |
delivered |
The provider confirmed delivery |
read |
The recipient opened or read it (where the provider reports it) |
bounced |
The address could not be reached |
complained |
The recipient reported it as spam |
failed |
The provider refused it. error says why |
suppressed |
Not sent because the address is on the do-not-contact list |
Later statuses arrive from the provider's callbacks, and a status only moves forward: a late delivered never overwrites a bounced. Some providers (SMTP) report nothing back, so their messages stop at sent. To receive callbacks, open the connection in the app and paste the webhook address it shows into your provider's dashboard.
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_ERROR |
No recipient, no body or template, template and connection channels differ, connection disabled, bad ids |
| 403 | FORBIDDEN |
The role lacks comms:send on this connection |
| 404 | NOT_FOUND |
No such connection, template or record |
A message to a do-not-contact address is still accepted (202): the returned message has status suppressed.
GET /orgs/{orgId}/comms/messages
Newest first. Returns an array of message objects.
| Parameter | Notes |
|---|---|
channel |
email, sms or whatsapp |
status |
One of the statuses above |
connection_id, record_id |
Filters |
limit |
Default 50, maximum 200 (a value above 200 falls back to 50) |
cursor |
The id of the last message of the previous page |
Message objects also carry body_text, body_html, template_id, record_type, record_id, provider_message_id, error, sent_at and delivered_at where they apply.
Templates
A template is a reusable message. Email templates have a subject, body_html and body_text; SMS has body_text; WhatsApp templates are the ones your provider has approved, linked by provider_ref. The text uses the same template language as PDF documents, with record values filled in from record_id, and data for anything extra. HTML values are escaped, so a value from a form cannot inject markup.
POST and PUT take key, name, channel, subject, body_html, body_text, object_type (the record type it is written for), provider_ref, provider_status and language. DELETE answers 204.
The do-not-contact list
Bounces, spam complaints and unsubscribes are added to this list automatically. A message to a listed address is never sent: it is logged with status suppressed, whichever route, flow or function asked for it. POST /comms/suppressions takes channel, address and an optional reason, and answers 204. GET takes channel, limit (default 200, maximum 500) and cursor, and answers an array. DELETE …/suppressions/{supId} answers 204.
A recipient who follows an unsubscribe link from one of your messages sees a confirmation page, and is added to the list when they confirm.
Related
- Functions SDK:
axis.comms.send - Flows: the send-message block
- In the product: Messaging