All docs

Send a Message

Endpoint reference for POST /v1/messages with full request and response shapes.

The single endpoint for outbound delivery. The same request handles iMessage, RCS, and SMS — pick channel: "auto" and we route through the fallback chain automatically.

Endpoint

POST https://api.senderz.com/v1/messages
Authorization: Bearer tf_live_<your_key>
Content-Type: application/json

Sandbox keys (tf_test_*) hit the same endpoint with synthetic delivery — see Testing.

Request body

FieldTypeRequiredDescription
tostring (E.164)yesRecipient phone, e.g. +15551234567
bodystringone of body/templateRaw message text, max 1600 chars
templatestringone of body/templateTemplate name; combine with data for variable substitution
dataobjectnoTemplate variable values, e.g. { "code": "1234" }
channelenumno, default autoauto / imessage / sms / rcs
from_numberstring (E.164)noPin the send to a tenant-owned phone; default uses the routing pool
message_typeenumno, default alertotp / alert / marketing — drives quiet-hours + warming policy

Examples

curl -X POST https://api.senderz.com/v1/messages \
  -H "Authorization: Bearer tf_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+15551234567",
    "body": "Hello from senderZ!",
    "channel": "auto"
  }'
import { SenderZ } from '@senderz/sdk'

const client = new SenderZ({ apiKey: process.env.SENDERZ_API_KEY })

const result = await client.messages.send({
  to: '+15551234567',
  body: 'Hello from senderZ!',
})

console.log(result.message_id)
import os
import requests

r = requests.post(
    'https://api.senderz.com/v1/messages',
    headers={
        'Authorization': f'Bearer {os.environ["SENDERZ_API_KEY"]}',
        'Content-Type': 'application/json',
    },
    json={'to': '+15551234567', 'body': 'Hello from senderZ!'},
)
r.raise_for_status()
print(r.json()['message_id'])

Successful response

HTTP/1.1 201 Created
Content-Type: application/json

{
  "message_id": "01HXY3M0KEXAMPLEMSGID",
  "status": "queued",
  "from": "+15555550100",
  "channel": "imessage",
  "estimated_delivery_ms": 1200
}

The id is at message_id (snake_case ULID). Store it; you’ll need it to correlate the delivery callback with the original send.

Errors

Body shape: { "error": string, "code": SCREAMING_CASE }.

HTTPcodeCause
400VALIDATION_ERRORMissing or malformed body field
401INVALID_API_KEYNo / wrong Bearer token
403OPTED_OUTRecipient is on the tenant’s opt-out registry
403QUOTA_EXCEEDEDMonthly new-contact cap hit
429RATE_LIMITEDPer-key rate cap exceeded; obey Retry-After

Field-name conventions

We use to (not destination/recipient), body (not text/message), from_number (not source/sender/from). If your client library exposes different names, map them on your side.

Pricing model

senderZ is flat-plan, not per-message. Plans charge by new contacts per month (unique numbers you’ve never messaged before). Messages to existing contacts are unlimited regardless of plan. See pricing for the plan tiers.

  • Webhooks — register your endpoint for delivery callbacks and inbound replies
  • Rate limits — per-key burst behavior and the 429 contract
  • Testing — sandbox keys and magic numbers
  • Compliance — STOP/HELP keywords and 10DLC stance