Sending SMS

Send one message or a thousand with the same simple endpoint shape. LynSMS handles encoding, segmentation, routing, and fallback — you handle the content.

Send a single message

POST/v1/messages/send
curl https://lynsms.com/api/v1/messages/send \
  -H "Authorization: Bearer ds_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to":      "+12025550143",
    "from":    "LynSMS",
    "body":    "Your verification code is 482194",
    "reference": "signup-9214"
  }'
const res = await fetch("https://lynsms.com/api/v1/messages/send", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LYNSMS_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "+12025550143",
    from: "LynSMS",
    body: "Your verification code is 482194",
    reference: "signup-9214",
  }),
});

if (!res.ok) {
  const err = await res.json();
  throw new Error(`LynSMS error: ${err.error.code} — ${err.error.message}`);
}

const { data: message } = await res.json();
console.log(message.id, message.status);
import os, requests

resp = requests.post(
    "https://lynsms.com/api/v1/messages/send",
    headers={
        "Authorization": f"Bearer {os.environ['LYNSMS_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "to":   "+12025550143",
        "from": "LynSMS",
        "body": "Your verification code is 482194",
        "reference": "signup-9214",
    },
    timeout=15,
)

resp.raise_for_status()
message = resp.json()["data"]
print(message["id"], message["status"])

Parameters

FieldTypeDescription
to requiredstringRecipient in E.164 format, e.g. +12025550143.
from requiredstringApproved sender ID. Up to 11 alphanumeric characters, or a numeric long/short code.
body requiredstringMessage body, up to 1,600 characters. Long messages are auto-segmented.
route_idintegerForce a specific route. If omitted, LynSMS picks the best one.
providerstringPreferred provider: twilio, sinch, infobip, clicksend.
referencestringYour correlation id, echoed back in webhooks.

Response

On success returns 201 Created with the new message object:

{
  "success": true,
  "data": {
    "id": "msg_VyB2pNkX0wnA9aTrLqDh1Z3Fc",
    "object": "message",
    "to": "+12025550143",
    "from": "LynSMS",
    "body": "Your verification code is 482194",
    "status": "sent",
    "cost": 0.0500,
    "currency": "USD",
    "provider": "twilio",
    "provider_message_id": "SM3f2c...",
    "attempts": 1,
    "created_at": "2026-05-12T11:34:21+00:00",
    "sent_at":    "2026-05-12T11:34:21+00:00"
  },
  "meta": { "request_id": "req_dXp9k..." }
}
How routing works. LynSMS tries your provider first (if specified), then walks the configured fallback chain. A provider that returns a retryable error (5xx, network timeout) hands off to the next provider; a hard error (validation, billing, blacklist) stops the chain so you don't get duplicate sends.

Send to many recipients

POST/v1/messages/bulk

Send the same body to up to 1,000 recipients in a single request. Each recipient is created as an independent message and reported back in the response.

curl https://lynsms.com/api/v1/messages/bulk \
  -H "Authorization: Bearer ds_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "LynSMS",
    "recipients": ["+12025550143", "+447700900100", "+4748640969"],
    "body": "Tickets for tonight are still available — bit.ly/lens-friday"
  }'
const res = await fetch("https://lynsms.com/api/v1/messages/bulk", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LYNSMS_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    from: "LynSMS",
    recipients: ["+12025550143", "+447700900100", "+4748640969"],
    body: "Tickets for tonight are still available — bit.ly/lens-friday",
  }),
});

const { data } = await res.json();
console.log(`${data.sent} sent, ${data.failed} failed`);
import os, requests

resp = requests.post(
    "https://lynsms.com/api/v1/messages/bulk",
    headers={"Authorization": f"Bearer {os.environ['LYNSMS_API_KEY']}"},
    json={
        "from": "LynSMS",
        "recipients": ["+12025550143", "+447700900100", "+4748640969"],
        "body": "Tickets for tonight are still available — bit.ly/lens-friday",
    },
)
data = resp.json()["data"]
print(f"{data['sent']} sent, {data['failed']} failed")

Returns a bulk_result:

{
  "success": true,
  "data": {
    "object": "bulk_result",
    "total": 3,
    "sent": 3,
    "failed": 0,
    "messages": [
      { "id": "msg_...", "to": "+12025550143", "status": "sent",  "cost": 0.05 },
      { "id": "msg_...", "to": "+447700900100", "status": "sent", "cost": 0.05 },
      { "id": "msg_...", "to": "+4748640969",   "status": "sent", "cost": 0.05 }
    ]
  },
  "meta": { "request_id": "req_dXp9k..." }
}
Per-recipient errors. A bulk request can partially succeed. Always inspect each item's status and error field instead of trusting the top-level HTTP code alone.

Segmentation & encoding

  • GSM-7 messages fit 160 chars per segment, then 153 per additional segment.
  • Unicode (UCS-2) — when the body contains any character outside GSM-7 — fits 70 chars per segment, then 67 per additional segment.
  • Each segment is billed individually. The cost field reflects the total.

Best practices

  • Use E.164 phone numbers (with the leading +). Local formats are rejected.
  • Pre-validate high-volume lists with HLR lookup before bulk sends.
  • Include an opt-out string on marketing messages — "Reply STOP to unsubscribe" — per local regulation.
  • Set a reference so you can correlate sends with your own system.