---
title: "Send an SMS message"
method: POST
path: "/sms"
tags: ["SMS"]
---

# Send an SMS message

`POST /sms`

## Request

- Base URL: `https://www.unitpost.com/api/v1`
- URL: `https://www.unitpost.com/api/v1/sms`
- Auth: HTTP bearer

Send a single SMS — now, or at a future time by passing `scheduled_at`. The recipient must be a valid E.164 number. It doesn't have to be one of your contacts: a number you've never stored is sent to without creating a contact (pass `create_contact` to add one), and opt-outs and the suppression list still apply. Marketing sends additionally require the recipient's prior express consent on record and honor recipient-local quiet hours (they are deferred to the next allowed window, never dropped). The sender is chosen by the recipient's country — the active number or Sender ID whose reach covers it, exactly as campaigns route — unless you pin one with `from` (a `snum_…` id from [List SMS numbers](#get-sms-numbers), or the number / Sender ID string). Pass an `Idempotency-Key` header to make retries safe: the same key with the same body replays the original message instead of sending (and billing) twice. Billing is per segment. Read delivery state back with [Retrieve an SMS message](#get-sms-id). Requires the `sms:send` capability.

## Request body

- SmsSend
  - `to` string, required: Recipient phone number in E.164 format, e.g. `+15551234567`.
  - `from` string: Optional sender to send from: a number id from `GET /sms/numbers` (`snum_…`), or the E.164 number / Sender ID string itself (e.g. `+18005550100`, `Acme`). Must belong to your workspace, be active, and be able to reach the recipient's country. Otherwise it answers `404 not_found`, `403 sender_not_approved`, or `422 sender_cannot_reach`. Omit to route automatically: Unitpost picks the active sender whose reach covers the recipient's country (10DLC before toll-free for US recipients; the matching Sender ID elsewhere), exactly as campaigns do.
  - `body` string, required: Message text. Billing is per segment (160 GSM-7 chars single-segment, 153 per segment concatenated; 70/67 for Unicode).
  - `type` 'transactional' | 'marketing': Marketing sends require the recipient's prior express consent on record and honor recipient-local quiet hours.
  - `scheduled_at` string, date-time: ISO-8601 time to send at. Omit to send immediately.
  - `create_contact` boolean: Create a contact for this recipient if one doesn't exist yet. Defaults to false. Sends don't modify your contact list. When the recipient is already a contact, the message is attached to them either way. A contact created this way records no consent, so it can't be used for marketing sends.

## Response `200`

Accepted for delivery or scheduled.

- Sms
  - `object` 'sms', required
  - `id` string, required
  - `to` string, required
  - `from` string, nullable, required
  - `direction` 'outbound' | 'inbound', required
  - `status` 'queued' | 'scheduled' | 'sending' | 'sent' | 'delivered' | 'failed' | 'suppressed' | 'canceled' | 'received', required
  - `type` 'transactional' | 'marketing', required
  - `body` string, nullable, required
  - `segments` integer, required
  - `country` string, nullable, required
  - `scheduled_at` string, nullable, required
  - `last_error` string, nullable, required: Legacy free-text error. Prefer `error.code`.
  - `error` object, nullable, required: Set on `suppressed` messages: the stable skip reason and its sentence. Null otherwise.
    - `code` 'out_of_reach' | 'unsupported_country' | 'no_phone' | 'opted_out' | 'no_marketing_consent' | 'quiet_hours_deferred' | 'sender_not_active', required: Why a message was skipped before reaching the carrier. `out_of_reach`: no sender covers the recipient's country; `unsupported_country`: no compliance rule for the country yet; `no_phone`: the contact has no SMS handle; `opted_out`: STOP / suppression / unreachable; `no_marketing_consent`: marketing without express consent; `quiet_hours_deferred`: delayed to the recipient's local window; `sender_not_active`: the sender lost approval.
    - `message` string, required: Human-readable sentence for `code`.
  - `created_at` string, required

## Other responses

- `401`: Missing or invalid API key.
- `403`: Insufficient scope; SMS is not on your plan (`limit_reached` — SMS is paid-plan only; a workspace whose paid plan just ended keeps sending through its grace window, then gets this); recipient not opted in (`recipient_not_opted_in`), suppressed (`message_suppressed`), no sender can reach the recipient's country (`unsupported_country`, the message names what to request), the `from` sender isn't active (`sender_not_approved`), the workspace has no active SMS sender yet (`channel_not_configured` — finish setup in the dashboard), or a brand's website is on a public abuse blocklist (`sms_brand_blocked` — contact support).
- `404`: `from` names a sender that isn't in your workspace.
- `409`: Idempotency conflict (same key, different body / in-flight).
- `422`: Validation error (`invalid_recipient_identity` for a malformed number; `sender_cannot_reach` when the `from` sender can't deliver to the recipient's country).
- `429`: Too many requests, or the sending wallet is exhausted.

---

[API](https://skmtc.dev/unitpost/apis/unitpost-api.md) · [All operations](https://skmtc.dev/unitpost/apis/unitpost-api/llms.txt) · [OpenAPI document](https://skmtc.dev/unitpost/apis/unitpost-api/revisions/86ff7c890967?raw)
