Messages

Sends a message to one or more recipients using a template. Supports multi-channel broadcast — when multiple channels are specified (e.g. ["sms", "whatsapp"]), a separate message is created for each (recipient, channel) pair. To choose which of your own numbers a send goes out from, use 'channels': {"sms": [{"from": ["+12125550000", "+14155550000"]}]}. Each channel holds a list of entries, each with 'from' and optionally 'country' and 'strategy'; 'country' and 'strategy' are stored but not acted on yet, so every entry's numbers apply to every recipient on that channel. Every number listed must be an active sender on your account. Like the other account-level preconditions below, that is checked per message rather than when the request is received: the request is still accepted with 202, and each affected message is reported as BLOCKED with error code BUSINESS_029 on GET /messages/{id} and the message.blocked webhook. Each channel's numbers restrict which numbers that channel may use; it does not choose channels — 'channel' does, and the two can be combined. With 'channel' left at auto-detect, a recipient best served by a channel you listed no numbers for still goes out on it. Where several of the listed numbers could serve a recipient, routing prefers the one whose area code matches theirs. Keys: sms, whatsapp, rcs, mms. Returns immediately with per-recipient message IDs for async tracking via webhooks or the GET /messages/{id} endpoint. Sends gated before any delivery attempt do not reject the request — an account-level precondition such as insufficient balance, a template not approved for sending, or free-form content with no open conversation with the contact. The send is accepted with 202 and the affected messages are reported as BLOCKED on GET /messages/{id} and the message.blocked webhook. To send later, set scheduled_at (ISO-8601 with an explicit UTC offset; a value without one is rejected) between 1 minute and 30 days ahead: the response is a ScheduledSendMessageResponse (the same fields plus scheduled_at; status is still QUEUED), each message then moves to SCHEDULED, is held and released at that time (within a few minutes), and a message.scheduled webhook fires once it is held. Balance and template approval are evaluated at release, not at acceptance. Quiet hours are not checked when the request is accepted: if the time falls inside a legally protected quiet-hours window for a recipient, that message is moved to the next allowed time at release and a second message.scheduled webhook reports the new scheduled_at. An account may hold at most 1,000,000 scheduled messages at once (429 LIMIT_001).

post/v3/messages

Request

  • Base URL
    https://api.sent.dm
  • URL
    https://api.sent.dm/v3/messages
  • Auth
    • API key in header x-api-key
    • or HTTP bearer

Headers

Idempotency-Keystring

Unique key to ensure idempotent request processing. Must be 1-255 alphanumeric characters, hyphens, or underscores. Responses are cached for 24 hours per key per customer.

x-profile-idstring uuid

Profile UUID to scope the request to a child profile. Only organization API keys can use this header. The profile must belong to the calling organization.

Request body

sandboxboolean

Sandbox flag - when true, the operation is simulated without side effects Useful for testing integrations without actual execution

tostring[]

List of recipient phone numbers in E.164 format (multi-recipient fan-out)

channelstring[] nullable

Channels to broadcast on, e.g. ["whatsapp", "sms"]. Each channel produces a separate message per recipient. "sent" = auto-detect. Defaults to ["sent"] (auto-detect) if omitted.

channelsobject nullable

Which of your own numbers to send from, keyed by channel, each channel holding a list of entries: {"sms": [{"country": "US", "from": ["+12125550000", "+14155550000"]}, {"from": ["+447700800001"]}]}. Any real channel may be a key; sent, which is auto-detect rather than a channel, is rejected. country and strategy are accepted and stored but not acted on yet: every entry's numbers apply to every recipient on that channel.

This does not choose channels — Channel does, and the two combine: "channel": ["sms"] with an sms list sends on SMS from those numbers. Each list only narrows which of its own channel's routes may win, so with Channel left at auto-detect a recipient best served by a channel with no list still goes out on it. Routing itself is unchanged: the same rules are scored and ranked the same way, with routes pinned to numbers you did not list removed from the running.

Each channel's list is independent: an sms list does not restrict MMS. To choose the numbers a message may go out from as MMS, name them under mms. MMS sends from your SMS numbers, so an mms list may name any of your active SMS numbers whose market carries MMS — the ones listed under mms in your channels.

Every number must be an active sender on your account. The request itself is still accepted (202) if one is not — like every other send-time rule, that is decided per message, so each affected message is recorded BLOCKED with error code BUSINESS_029 and reported on GET /v3/messages and the status webhook.

textstring nullable

Plain-text (free-form) message body. Provide either Template or this.

media_urlsstring[] nullable

Attachments for this send, as publicly fetchable https URLs. Used by the MMS channel and ignored by every other one.

Supplying these replaces the media on the template's mms body rather than adding to it, so a template can hold a default creative while a caller still sends something recipient-specific.

Their presence is also what makes a message eligible for MMS on an auto-detect send: a message with nothing attached is delivered as SMS, because an MMS with no media is a more expensive text message. For the same reason a send that names mms in channel with nothing attached, here or on the template, is refused with a 400.

The recipient's carrier fetches each URL after the send is accepted, so it must stay publicly reachable — a link that expires, or one behind auth, arrives as a failed message.

subjectstring nullable

Subject line for this send, overriding the template's. MMS only; ignored on every other channel. Most handsets render it above the body, some ignore it entirely.

scheduled_atstring date-time nullable

Optional future send time as an ISO-8601 timestamp with an explicit UTC offset, e.g. 2026-10-01T09:00:00+02:00 or 2026-10-01T07:00:00Z. A value without an offset is rejected (400) rather than read in the server's zone. The offset only fixes the instant: it is stored and echoed in UTC as scheduled_at. Omit to send now. Must be at least one minute ahead and at most 30 days ahead. Accepted messages report SCHEDULED and are released for delivery at this time. Quiet hours, balance and template approval are evaluated at release, not at acceptance: a message whose time falls inside a recipient's protected quiet-hours window is moved to the next allowed time and a second message.scheduled webhook reports the new scheduled_at.

Example request

{
  "to": [
    "+14155551234",
    "+14155555678"
  ],
  "channel": [
    "sms",
    "whatsapp"
  ]
}

Response

Message accepted for processing (QUEUED). data is a SendMessageResponse, or for a request that carried scheduled_at a ScheduledSendMessageResponse: the same fields plus scheduled_at. Status is QUEUED in both, and each scheduled message moves to SCHEDULED once it is held

successboolean

Indicates whether the request was successful

Changes

    • breaking

      added subschema #2: SentDmServicesEndpointsCustomerAPIv3MessagesRequestsSendMessageV3Request_Properties to the request body allOf list

    • breaking

      added to the response property oneOf list for the response status

    • breaking

      added to the response property oneOf list for the response status

    • breaking

      added to the response property oneOf list for the response status

    • breaking

      added to the response property oneOf list for the response status

    • warning

      removed subschema #2: SentDmServicesEndpointsCustomerAPIv3RequestsSendMessageV3Request_Properties from the request body allOf list

    • info

      the endpoint scheme security BearerAuth was added to the API

    • info

      added the non-success response with the status

    • info

      removed the non-success response with the status

    • info

      added to the response property allOf list for the response status

    • info

      added to the response property allOf list for the response status

    • info

      added to the response property allOf list for the response status

    • info

      added to the response property allOf list for the response status

    • info

      added to the response property allOf list for the response status

    • info

      removed from the response property allOf list for the response status

    • info

      removed from the response property allOf list for the response status

    • info

      removed from the response property allOf list for the response status

    • info

      removed from the response property allOf list for the response status

    • info

      removed from the response property allOf list for the response status

    • info

      removed from the response property oneOf list for the response status

    • info

      removed from the response property oneOf list for the response status

    • info

      removed from the response property oneOf list for the response status

    • info

      removed from the response property oneOf list for the response status

    • breaking

      added to the / request property allOf list

    • breaking

      removed from the / request property oneOf list

    • breaking

      the / request property type changed from no type to object

    • info

      added the new optional request property /

    • info

      the request property / became nullable