Send an SMS

View as Markdown

Send a text message from one of your Dial numbers to any phone number. You need two things: the recipient in E.164 format (e.g. +14155550123) and the id of the Dial number you’re sending from (run dial number list to find it).

$dial message \
> --to +14155550123 \
> --body "Hello from Dial" \
> --from-number-id pn_123

The message comes back with an id and a deliveryState of pending, which advances to delivered (or undelivered/failed, with a deliveryError reason) as the carrier reports back. You don’t have to poll for it: each advance is published as a message.status_changed event, so streaming events tells you the moment a message lands or is rejected.

Delivery and read state

Every message carries two independent fields, because delivery and reads are separate facts and not every rail reports both:

FieldValuesNotes
deliveryStatepending, delivered, undelivered, failed, unconfirmedunconfirmed means the rail sends no delivery receipts (iMessage numbers) — only a read can upgrade it.
readStateunread, read, unsupportedunsupported means the rail never reports reads (SMS). Nothing is coming — don’t wait for it.
readAttimestamp | nullWhen the recipient read the message; null if unread or unsupported.
deliveryErrorstring | nullPlain-language failure reason. Non-null only when deliveryState is undelivered or failed.

A read receipt proves delivery, so a message that is read is also reported as delivered.

Inbound messages report deliveryState: "delivered" (they arrived) and readState: "unsupported" — Dial doesn’t track whether you read an inbound message.

Migrating from status. The older status and statusError fields are still returned and still describe delivery only — no change in behavior. statusError and deliveryError are identical. status and deliveryState are the same axis in two vocabularies, and differ on two values:

statusdeliveryState
sentpending
unknownunconfirmed
delivered / undelivered / failedsame

Prefer deliveryState: it names those two states for what they mean, and it pairs with readState. Note that status no longer returns read — reads now live on readState and readAt, so a read message reports status: "delivered".

Accounts on a subscription can send SMS only to US numbers — any other destination is rejected with a 400. Pay-as-you-go accounts can send to any supported destination.

Attach media (MMS)

Attach up to 10 media items (5 MB each) to a message. Supply each one either as a local file — the CLI and SDKs upload it for you — or as a public URL that Dial downloads server-side. Either way Dial mirrors the bytes into its own storage and serves them from a stable public URL, returned in the message’s media array.

Supported types: images (jpeg, png, gif, webp, bmp), audio (mp3, m4a, ogg, wav, amr), video (mp4, 3gpp), pdf, vCard (.vcf), and iCalendar (.ics). Any other type is rejected with a 400. Note that HEIC (the default iPhone photo format) and SVG are not supported — convert HEIC to JPEG/PNG before sending.

$dial message \
> --to +14155550123 \
> --body "Here's your call summary" \
> --from-number-id pn_123 \
> --media ./summary.png \
> --media https://your-cdn.example.com/chart.jpg
Dial sends over SMS. The to and from numbers must both be in E.164 format.
True MMS delivery is supported only for US and Canada numbers. To other destinations, the carrier delivers the message as an SMS containing a link to the media rather than an inline attachment (and returns an error if that conversion is disabled). The media itself is still hosted by Dial at the media[].url either way.
iMessage numbers. A message with media sent from an iMessage number is delivered as native attachments when the recipient supports rich messaging (iMessage or RCS) — text and media arrive together as one message. When the recipient supports neither (e.g. an Android device without RCS), Dial delivers the same message as a text with the media links appended, from the same number. The message’s media array is identical either way.
Sending isn’t idempotent — there’s no idempotency key, so a retry sends a second SMS. If a request fails ambiguously, confirm before re-sending rather than blind-retrying.

Next