Speko Docs
SMS

Send SMS messages

Queue single, batch, and scheduled 10DLC messages and track delivery safely.

Every send is durable and asynchronous. Speko stores the message, compliance decision, campaign snapshot, and credit reservation before enqueueing it, then returns 202. A response of queued or accepted never means carrier delivery.

Send one message

curl -X POST https://api.speko.dev/v1/sms/messages \
  -H "Authorization: Bearer $SPEKO_API_KEY" \
  -H "Idempotency-Key: order-982-confirmation" \
  -H "Content-Type: application/json" \
  -d '{
    "from_phone_number_id": "8f0e9a96-...",
    "to": "+12025550123",
    "text": "Acme: your order 982 is confirmed. Reply STOP to opt out.",
    "recipient_timezone": "America/New_York",
    "metadata": { "order_id": "982" }
  }'

The sender must belong to your workspace, be active and outbound-capable, show a ready messaging profile, and have an ASSIGNED approved campaign. You never provide a campaign or brand ID; Speko derives and snapshots both from the sender.

Idempotency-Key is required. Reusing the key with the same JSON returns the original message. Reusing it with different JSON returns 409 IDEMPOTENCY_CONFLICT. Persist the key before calling Speko and reuse it after client timeouts.

Schedule and quiet hours

Add an RFC 3339 send_at up to 90 days ahead. Proactive traffic is allowed from 08:00 through 21:00 recipient local time. Speko resolves timezone from the active consent, conversation, request, or organization default and shifts work to the next valid boundary when needed.

The response exposes both requested_send_at and effective_send_at. A proactive send with no resolvable IANA timezone fails with RECIPIENT_TIMEZONE_REQUIRED. Consent, suppression, readiness, credits, and quiet hours are rechecked when scheduled work becomes due.

Send a batch

POST /v1/sms/batches accepts one sender and 1–1,000 recipient rows:

{
  "from_phone_number_id": "8f0e9a96-...",
  "send_at": "2026-09-03T14:00:00Z",
  "recipients": [
    {
      "to": "+12025550123",
      "text": "Acme: your appointment is tomorrow at 10 AM.",
      "recipient_timezone": "America/New_York",
      "metadata": { "appointment_id": "apt_1" }
    }
  ]
}

Use a batch-level Idempotency-Key. Sender or envelope errors reject the whole request. Recipient-specific issues create rejected message rows while valid rows continue. Batch traffic is always proactive and cannot use the 24-hour conversational reply exception.

Read progress at GET /v1/sms/batches/{batch_id} and row results at GET /v1/sms/batches/{batch_id}/messages. Cancel remaining queued/scheduled rows with POST /v1/sms/batches/{batch_id}/cancel.

Status lifecycle

StatusMeaning
queued / scheduledDurable and waiting for processing or its effective time
submittingProvider submission has begun; no longer cancelable
accepted / sentAccepted by Telnyx or handed to the carrier
deliveredAuthoritative delivery receipt received
delivery_failedCarrier reported a terminal delivery failure
rejectedRejected locally before submission
submission_unknownThe request may have reached Telnyx; Speko will not blindly retry
canceledCanceled before provider submission
receivedInbound message

Poll GET /v1/sms/messages/{message_id}, subscribe to signed webhooks, or use GET /v1/sms/stream for a live dashboard. Delivery events are idempotent and later authoritative receipts can repair out-of-order state.

Limits

  • SMS only; MMS is not accepted.
  • Recipients must be E.164.
  • Text is limited to 1,600 characters.
  • Metadata allows 50 keys and 8 KB serialized.
  • Only queued and scheduled messages can be canceled.

On this page