Send a Message


Send an SMS or MMS to a single recipient.

Endpoint

POST /api/v1/messaging/message

Request body

FieldTypeRequiredDescription
tostringYesRecipient phone number in E.164 format (e.g., +18005551234)
fromstringYesSender phone number in E.164 format or short code (5-6 digits). Must be an active number assigned to your account.
bodystringConditionalMessage text, up to 640 characters. Required for SMS. Optional for MMS if mediaUrl is provided.
mediaUrlstringNoPublicly accessible URL to a media file. When provided, the message is sent as MMS.
idempotencyKeystringNoUnique key to prevent duplicate sends on retries. See Idempotency.

SMS example

curl -X POST https://platform.textingline.com/api/v1/messaging/message \
  -u "your_key_id:your_key_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+18005551234",
    "from": "+18005559876",
    "body": "Your verification code is 482910"
  }'

MMS example

curl -X POST https://platform.textingline.com/api/v1/messaging/message \
  -u "your_key_id:your_key_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+18005551234",
    "from": "+18005559876",
    "body": "Check out our new collection!",
    "mediaUrl": "https://example.com/images/promo.jpg"
  }'

Response

{
  "id": "msg_DsAdb4EBYBh3z25sBA1qnv",
  "direction": "outbound",
  "to": "+18005551234",
  "from": "+18005559876",
  "body": "Your verification code is 482910",
  "status": "sent",
  "sentAt": "2026-03-15T14:30:05.000Z",
  "createdAt": "2026-03-15T14:30:00.000Z"
}
FieldDescription
idUnique message identifier, prefixed with msg_
directionMessage direction: outbound or inbound
statusOne of sent, queued, or failed
errorTypeError classification. Present only when status is failed. See values below.
errorMessageHuman-readable error description. Present only when status is failed.
sentAtISO 8601 timestamp of when the message was sent to the provider. Absent when status is failed.
createdAtISO 8601 timestamp of when the message was created

Merge tags

The body field supports merge tags like {{contact_first_name}} and {{contact_custom_field_1}}. If the recipient's phone number matches a contact in your account's contact book, the platform substitutes the contact's values before the message is sent.

curl -X POST https://platform.textingline.com/api/v1/messaging/message \
  -u "your_key_id:your_key_secret" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+18005551234",
    "from": "+18005559876",
    "body": "Hi {{contact_first_name}}, your appointment is confirmed for 3 PM."
  }'

If +18005551234 has a stored firstName of Alice, the recipient sees Hi Alice, your appointment is confirmed for 3 PM..

Missing data resolves to an empty string — no error is raised. If no contact exists for the recipient's phone number, OR a contact exists but the referenced field is empty (e.g. the contact has no firstName), the variable resolves to an empty string and the recipient sees Hi , your appointment is confirmed for 3 PM.. The message is sent in both cases.

Merge tags are substituted in body only — mediaUrl is not templated. Putting {{contact_first_name}} in mediaUrl sends a literal request to that URL with the braces intact (and likely 404s).

See Merge Tags for the full variable list (contact-scoped, custom fields, date helpers, tracking links) and design guidance for templated bodies.


Recipient compliance checks

Before a single send is forwarded to the provider, the recipient runs through the same compliance gate used by broadcasts and batches:

  • Opt-in filter — if the recipient is not opted in to any group owned by your account, the request fails with error code 33028.
  • Hard opt-out filter — if the recipient has explicitly refused messages (texted STOP, opted out at a kiosk, or was flagged via carrier propagation), the request fails with error code 33029. This is a permanent refusal; re-engaging requires fresh consent from the contact themselves.

Both errors return 400 Bad Request and prevent the message from being queued or charged.


Failed responses

When the message is accepted but the provider cannot deliver it (e.g., the recipient has opted out at the provider level), the API returns a 201 response with status: failed instead of an HTTP error. This allows you to distinguish between request-level errors (4xx/5xx) and delivery-level failures.

{
  "id": "msg_Hp2sABcDeFgHiJkLmNoPqR",
  "direction": "outbound",
  "to": "+18005551234",
  "from": "+18005559876",
  "body": "Your verification code is 482910",
  "status": "failed",
  "errorType": "opted_out",
  "errorMessage": "The recipient has opted out from receiving messages from this sender.",
  "createdAt": "2026-03-15T14:30:00.000Z"
}

Error types

See Error Codes — Message error types for the full list of errorType values and their descriptions.


Credits

Each message consumes credits from your account balance:

  • SMS: 2 credits (1 message + 1 carrier lookup)
  • MMS: 2 SMS credits + 1 MMS credit

If your balance is insufficient, the request is rejected with error code 33023. Check your credit balance in the dashboard.


Rate limits

The send message endpoint allows 60 requests per minute. See Rate Limits for details.


Common errors

CodeDescription
33004The from number is not assigned to your account
33009The from number is not in an active state
33016Message body contains unsupported characters
33019Message violates compliance rules
33023Insufficient credits

See Error Codes for the full reference.