Sending
Sending messages
Every field of a send, which ones are required, and how batches, replies, scheduling and cancelling work.
POST /v1/messages needs three things: who sends, who receives and what to say. Everything else is optional.
The request#
| Field | Type | Needed | What it does |
|---|---|---|---|
line_id |
string (uuid) |
One of line_id, channel_id, from when the key is not limited to one line |
The line to send from. A key limited to one line may leave all sender fields out. |
channel_id |
string (uuid) |
One of line_id, channel_id, from when the key is not limited to one line |
Send from this channel, on the line it is the primary sender of, else on its default line. |
from |
string |
One of line_id, channel_id, from when the key is not limited to one line |
A channel address (for SMS, the number); resolves like channel_id. |
channel_type |
string |
Optional | Only to choose between channels of different types that share the from address. |
prefer |
sms | whatsapp |
Optional | The channel type to try first when the line picks the sender; beats the line's prefer. When that type has no healthy sender or cannot reach the recipient, the line's next type sends. |
to |
string |
Exactly one of to, contact_id |
Recipient address in the channel's format (a phone number in any accepted format for SMS). Not together with contact_id. |
contact_id |
string (uuid) |
Exactly one of to, contact_id |
Send to this contact's address for the channel. Not together with to. |
content |
object |
At least one of content, body, media_ids, template_id |
The full content form. type is text, media, template or a channel-specific type refused with CHANNEL_CAPABILITY_UNSUPPORTED. Excludes body, media_ids, template_id and variables. |
body |
string |
At least one of content, body, media_ids, template_id |
Shorthand for text content. May go with media_ids. |
media_ids |
array of uuid | At least one of content, body, media_ids, template_id |
Shorthand for media content: ids from POST /v1/media. One item today. |
template_id |
string (uuid) |
At least one of content, body, media_ids, template_id |
Shorthand for template content. |
variables |
object |
Needed when template_id is sent | Values for the template's variables. Needs template_id. |
channel_options |
object |
Optional | Options only some channels understand; ignored with a warning elsewhere. |
smart_replace |
boolean |
Optional | Replace characters that force UCS-2 encoding with plain look-alikes where it saves segments. |
category |
transactional | marketing |
Needed when lane is bulk | Required when lane is bulk (CATEGORY_REQUIRED). |
lane |
high | normal | bulk |
Optional | How urgently the message is sent. bulk is paced behind other traffic and needs category. |
send_after |
string (date-time) |
Optional | At most 30 days ahead (SEND_AFTER_TOO_FAR). A time in the past means now. |
expires_in |
integer 60–604800 |
Optional | Seconds after which an unsent message is dropped, 60 to 604800 (7 days). |
reference_id |
string |
Optional | Your own id for this message, 1 to 100 characters without spaces. Unique per account forever (REFERENCE_ID_EXISTS). |
metadata |
object |
Optional | Up to 50 keys, 2 KB as JSON, scalar values only. Returned on the message and in webhooks. |
shorten_links |
boolean |
Optional | Replace each URL with a short link so clicks are counted. |
dry_run |
boolean |
Optional | Validate, render and compute segments and holds without creating anything (answers 200). |
The table is generated from the API contract, so it always matches the reference. The rules that depend on other fields:
- Recipient: exactly one of
toandcontact_id. - Content: at least one of
content,body,media_idsandtemplate_id.contentis the full form;bodyandmedia_idsare the shorthand for text and image messages.contenttogether with a shorthand field is refused withCONTENT_CONFLICT.variablesonly goes withtemplate_id. - Sender: four optional fields say who sends.
line_idnames the line; the line picks its first healthy sender and may move the message to the next one later.channel_id(orfrom, the channel's address) forces one channel: the message issender_pinnedand never moves. Withoutline_idit goes out on the line that has that channel as its primary, else on the channel's default line, so with a line-limited key sendline_idtoo.channel_typeonly chooses between channels that share afromaddress (FROM_AMBIGUOUS). A key limited to one line may leave all four out. - Line errors:
LINE_REQUIREDwhen nothing names a line and the key is not limited to exactly one;CHANNEL_LINE_MISMATCHwhen the fields point at different lines or the channel is not a sender of the line;LINE_HAS_NO_SENDERSwhen the line has no enabled sender;LINE_ARCHIVEDwhen the line is archived. A line whose senders are all unhealthy still accepts the message and holds it (hold_reason: no_sender_available) until one recovers. - Category: required when
laneisbulk, and always in a batch.
A request that breaks the shape rules answers 422 VALIDATION_FAILED with every broken field in details.fields, so you fix
them in one round trip. Rules that depend on state, such as an opt-out or a number that is not valid, have their own codes.
A text message#
{
"line_id": "0198e0b4-9411-7f60-a2d9-1c8b6e3a4d03",
"to": "+923001234567",
"body": "Your order #1094 is ready for pickup.",
"category": "transactional",
"reference_id": "order-1094",
"metadata": {"order_id": "1094"}
}
Phone numbers may be written in any accepted format; MyComm normalises them to E.164. A number that cannot be parsed answers
422 INVALID_NUMBER.
Content forms#
| You want | Send |
|---|---|
| Text | body |
| An image (MMS) | upload with POST /v1/media, then media_ids: [id] (one item today), with or without body |
| A saved template | template_id and variables |
| Something channel-specific | content and channel_options; a content type or option the channel cannot carry answers 422 CHANNEL_CAPABILITY_UNSUPPORTED |
Count segments before you send: characters outside the GSM-7 alphabet switch a message to UCS-2 and shorten each segment.
dry_run returns the computed encoding and segments; smart_replace: true swaps look-alike characters that force UCS-2.
Lane and category#
category says why you are sending; lane says how urgently.
| Message | Category | Lane | expires_in |
|---|---|---|---|
| One-time code | transactional |
high |
600 (10 minutes) |
| Appointment reminder | transactional |
normal |
86400 (24 hours) |
| Invoice | transactional |
normal |
259200 (72 hours) |
| Promotion | marketing |
bulk |
86400 (24 hours) |
marketing is refused for recipients who opted out (RECIPIENT_OPTED_OUT) and in countries where it gets SIMs blocked
(MARKETING_NOT_ALLOWED). Pick the category the recipient would expect; content that reads as a promotion but is sent as
transactional is refused with CATEGORY_MISMATCH.
Scheduling and expiry#
send_after holds the message until that time, at most 30 days ahead (SEND_AFTER_TOO_FAR); a time in the past means now.
expires_in (60 to 604800 seconds) drops a message that has not been sent by then, so an old one-time code is never sent late.
Idempotency and your own ids#
Send an Idempotency-Key on every send so a retry cannot send twice; the next page has the rules. reference_id is your own
id for the message, unique per account forever. Reusing one answers 409 REFERENCE_ID_EXISTS with the existing message in
details.message, which makes it a second safety net, but it does not replace the idempotency key.
Send many#
POST /v1/messages/batch takes up to 1,000 messages and optional defaults that fill any field an item leaves out. Each item
gets its own result in results[], with the index of the item, so one bad number does not stop the others. A batch needs a
category, in the item or in defaults. Send "dry_run": true to check a whole batch first.
{
"defaults": {"category": "transactional"},
"messages": [
{"to": "+923001234567", "body": "Your order #1094 is ready.", "reference_id": "order-1094"},
{"to": "+923007654321", "body": "Your order #1095 is ready.", "reference_id": "order-1095"}
]
}
Reply in a conversation#
POST /v1/conversations/{id}/messages replies on the conversation's line to its contact, so you do not repeat the sender or
the recipient; it goes out through the conversation's channel while that is a healthy sender of the line. It needs
messages:send and conversations:write.
Cancel and retry#
POST /v1/messages/{id}/cancel cancels a queued message, held and scheduled ones included (200, status: cancelled). A
leased message is already on the phone, so the call is a request: it answers 202 with the message still leased and
cancel_requested_at set, and the phone's next report decides the outcome. A message that was sent answers
409 NOT_CANCELLABLE.
POST /v1/messages/{id}/retry queues a failed or expired message again under the same id and reference_id, up to five
manual retries (409 RETRY_LIMIT_REACHED). A message that failed with UNCONFIRMED may already have been delivered, so the
retry needs acknowledge_possible_duplicate: true. Many failures are retried by the platform without your help; see
the message lifecycle.
Warnings#
A successful answer carries warnings[]: things that did not stop the message but that you may want to act on, for example
CHANNEL_OPTIONS_IGNORED when you sent options the channel does not use.