Documentation
Outbound SMS
Send production SMS through suppression, spend, sender, and provider checks.
Outbound sends move through the same safety path whether they originate from the API, message composer, campaign builder, or workflow engine.
Request examples
curl
curl https://api.texttree.ai/api/v1/messages \
-H "Authorization: Bearer $TEXTREE_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+15551234567",
"body": "Your appointment is tomorrow at 9 AM.",
"metadata": {
"workflow": "appointment-reminder"
}
}'
JavaScript
const response = await fetch("https://api.texttree.ai/api/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TEXTREE_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
phone_number: "+15551234567",
body: "Your appointment is tomorrow at 9 AM.",
metadata: { workflow: "appointment-reminder" },
}),
});
const result = await response.json();
Python
import os
import requests
result = requests.post(
"https://api.texttree.ai/api/v1/messages",
headers={"Authorization": f"Bearer {os.environ['TEXTREE_ACCESS_TOKEN']}"},
json={
"phone_number": "+15551234567",
"body": "Your appointment is tomorrow at 9 AM.",
"metadata": {"workflow": "appointment-reminder"},
},
timeout=10,
).json()
Elixir
Mix.install([:req])
result =
Req.post!(
"https://api.texttree.ai/api/v1/messages",
auth: {:bearer, System.fetch_env!("TEXTREE_ACCESS_TOKEN")},
json: %{
phone_number: "+15551234567",
body: "Your appointment is tomorrow at 9 AM.",
metadata: %{workflow: "appointment-reminder"}
}
).body
Every send is checked for workspace suppressions, spend limits, ledger reservation, sender availability, payload validation, and provider readiness before provider execution. Campaign sends also check campaign-specific suppressions before they enqueue a TextTree message and again if a campaign suppression is added before final dispatch.
The dashboard quick-send form and the Send page use the same queueing path as
the API. Operators can choose a connected branded sender number in the app, or
leave sender selection automatic. A selected sender must still be connected to
the workspace when the worker dispatches the message; if it is unavailable, the
job ends with sender_number_unavailable and does not fall back to another
sender.
Message idempotency includes the selected sender. Reusing an idempotency_key
with a different recipient, body, cost, or sender is an idempotency conflict.
Failure behavior
If a send is blocked, the API returns a structured failure and the message log shows the same reason in the timeline.
{
"error": {
"code": "workspace_suppressed",
"message": "Recipient is suppressed for this workspace.",
"fix": "Respect the suppression or remove it only after your compliance process allows SMS again."
}
}