Sending
Send an SMS or MMS from an SMS-enabled number with POST /v1/sms/messages, schedule it, read opt-outs and look up a number's line type.
When you finish this page you have sent an SMS and an MMS from your number and know how replies and delivery reach you. You need an SMS-enabled number (approved for US numbers) and usage-based billing. from also accepts a branded sender ID such as ZERNIO for one-way international sends, with no number and no US registration.
Step 1: Send an SMS
Call POST /v1/sms/messages with from, to and text. Both numbers are normalized to E.164, so from matches however you format it, and replies thread into the same inbox conversation. text is at most 10 segments (1,530 GSM-7 or 670 unicode characters).
import Zernio from '@zernio/node';
const zernio = new Zernio();
const { data: sent } = await zernio.sms.sendSms({
body: {
from: '+14155550100',
to: '+13105551234',
text: 'Acme: your order #1234 has shipped. Reply STOP to opt out.',
}
});
console.log(sent.id, sent.status);Response (200):
{
"id": "67c9d0e1f2a3b4c5d6e7f8a9",
"conversationId": "66c3d2ae7b4f6c8d0e1f2a3b",
"status": "sent"
}Delivery and replies arrive as webhooks, not by polling: this message's outcome fires message.delivered or message.failed (with the carrier's error code), an inbound reply fires message.received with platform: "sms", and the first message of a new thread also fires conversation.started. Send an Idempotency-Key header so a retry replays the original response instead of sending twice (idempotency).
A US number must have an approved carrier registration before you can send. Until the registration reaches approved the number stays inactive, and from matches active numbers only, so the send comes back as a 404.
Step 2: Send an MMS
Add mediaUrls (public URLs, up to 10) to attach media. Pass text too for a caption, or omit it to send media only.
curl -X POST "https://zernio.com/api/v1/sms/messages" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "+14155550100",
"to": "+13105551234",
"text": "Here is your receipt",
"mediaUrls": ["https://acme.example.com/receipts/1234.png"]
}'Response (200):
{
"id": "67c9d0e1f2a3b4c5d6e7f8aa",
"conversationId": "66c3d2ae7b4f6c8d0e1f2a3b",
"status": "sent"
}To send the same message to many contacts, create an SMS broadcast instead. Its message.attachments (up to 10, each { url }) go out as MMS media, and a broadcast with attachments may omit text. Each URL must be public http(s) and must not redirect; JPEG, PNG, GIF, WEBP, MP4 or 3GPP under 1 MB is checked at create when the host answers a HEAD request. A private or internal host, a redirecting URL, more than 10 attachments or a file over 1 MB is a 400, on create and on update. Delivery receipts update the broadcast's delivered and failed counters.
To send later, add sendAt as an ISO 8601 time with offset, for example "sendAt": "2027-01-01T12:00:00Z"; it must be in the future.
Opt-outs
Recipients who reply STOP are opted out by the carrier, and further sends to them are refused with 409, never silently dropped. Only the recipient can re-subscribe, by replying START. Call GET /v1/sms/opt-outs (or format=csv) to keep your own suppression list in sync. limit defaults to 500 and tops out at 5,000: the list is truncated to it, most recent first, and there is no cursor, so raise limit rather than paging.
curl "https://zernio.com/api/v1/sms/opt-outs" \
-H "Authorization: Bearer $ZERNIO_API_KEY"Response (200):
{
"optOuts": [
{ "phoneNumber": "+13105559999", "optedOutAt": "2027-01-02T09:30:00Z", "keyword": "STOP", "from": "+14155550100" }
],
"count": 1
}Look up a number
Call GET /v1/sms/lookup with number for its carrier and line type (mobile, landline, voip, toll-free, unknown) plus smsReachable. Each lookup is billed by the carrier-data provider, so call it when you validate an opt-in list, not on every send.
curl "https://zernio.com/api/v1/sms/lookup?number=%2B13105551234" \
-H "Authorization: Bearer $ZERNIO_API_KEY"Response (200):
{
"phoneNumber": "+13105551234",
"carrierName": "T-Mobile USA",
"lineType": "mobile",
"smsReachable": true
}If it fails
A 409 means the recipient replied STOP, or the same Idempotency-Key is still in flight:
{
"error": "Recipient +13105559999 has opted out of SMS from this number",
"type": "invalid_request_error"
}Drop the recipient from your list; they come back only by replying START. A 404 means no active SMS-enabled number matches from, which covers both a number you do not own and a US number whose registration has not been approved; a 422 means the Idempotency-Key was reused with a different body. Every error uses the envelope in error handling.
Related
- Carrier registration: the US step before sending.
- Sender IDs: send from a brand name internationally.
- Send an SMS/MMS: every field of the request.
- Inbox webhooks:
message.received,message.deliveredandmessage.failedpayloads. - SMS rates: per-segment prices by destination.
Carrier Registration
Register a US number for SMS with 10DLC or toll-free verification through POST /v1/sms/registrations, reuse an approval on more numbers, and appeal a rejection.
Sender IDs
Send one-way international SMS from a brand name like ZERNIO instead of a phone number, with POST /v1/sms/sender-ids and the regular send endpoint.