Skip to content
Get Started
Getting Started

Sending messages

Outbound messages documentation | iMessage for Business

The Sendblue API conforms to REST API standards. Using it, you can send outbound iMessage and SMS to any enabled phone globally.

In this documentation, we will cover how to:

  1. Send iMessages to a destination phone number
  2. Track the status of your message using webhooks

There are two endpoints for sending messages:

ParameterTypeRequiredDescription
numberstringYesE.164 formatted phone number of the recipient (e.g., “+19998887777”)
from_numberstringConditionalYour Sendblue line in E.164 format. Optional on dedicated and enterprise ordinary direct sends; capacity-aware selection requires account enablement. Required on free shared-line plans.
contentstringNoThe message content to send
media_urlstringNoURL to media file to send (see Sending files)
app_cardobjectNoApp Card identity, state, and layout (see App Cards)
send_stylestringNoExpressive message style (see Expressive messages)
status_callbackstringNoURL to receive status updates for this message
seat_idstringNoIdentifies the seat (user) sending the message. Accepts either the seat UUID or Firebase Auth subject. When provided, the seat’s email is auto-populated as sender_email on the message and webhook payloads. List seats via the Seats API.
reply_toobjectNoInline reply target with message_handle (see Inline replies)
pre_reply_overridebooleanNoLet one otherwise-blocked message through to a contact who has not replied yet, once per contact per day (see Before the first reply)

You can send images using the media_url parameter. This URL should be a CDN link pointing to an image. The URL must end with the proper file extension. We recommend sticking to the standard PascalCase naming convention. So if your image is named “Welcome Image”, we recommend uploading it to your CDN as “WelcomeImage.png”.

File size limits vary by message type:

ServiceRecommendedMaximum
iMessage20 MB100 MB
SMS—5 MB
RCS—5–100 MB (varies by carrier and file type)

We recommend keeping attachments under 20 MB for the most reliable delivery across all message types. The 100 MB maximum applies to all file types including videos when sending via iMessage.

Files with the .caf extension are rendered as voice memos on the recipient’s device. All standard video file types (e.g., .mp4, .mov) render as video. See the Voice notes guide for details.

media_url does not support signed urls, please use the media object pattern for that.

Sendblue will POST the endpoint you provide in status_callback whenever the status of a message changes to one of the following:

  • REGISTERED
  • PENDING
  • DECLINED
  • QUEUED
  • ACCEPTED
  • SENT
  • DELIVERED
  • ERROR

Below is an example of the POST body that is sent to the status_callback URL for a sent message:

{
"accountEmail": "your-account-email",
"content": "Hello world!",
"is_outbound": true,
"status": "SENT",
"error_code": null,
"error_message": null,
"error_key": null,
"error_reason": null,
"message_handle": "5a17319e-cbcf-443e-897e-d8b0c04b1b09",
"date_sent": "2025-12-12T15:35:35.410Z",
"date_updated": "2025-12-12T15:35:35.410Z",
"from_number": "+18649820355",
"number": "+19998887777",
"to_number": "+19998887777",
"was_downgraded": null,
"plan": "dedicated",
"media_url": "",
"message_type": "message",
"group_id": "",
"participants": [],
"send_style": "",
"opted_out": false,
"error_detail": null,
"sendblue_number": null,
"service": "iMessage",
"group_display_name": null
}
Callback Body FieldTypeDescription
accountEmailstringAssociated account email
contentstringMessage content
is_outboundbooleanTrue if message is sent, false if message is received
statusstringThe current status of the message
error_codeintError code (null if no error)
error_messagestringThe rule’s key when a rule declined the message (same as error_key), otherwise an error description (null if no error)
error_keystringStable key of the rule that declined the message (e.g. OPTED_OUT, PRE_REPLY_LIMIT_REACHED). Branch on this field. Null when no rule declined the message
error_reasonstringHuman-readable text for display, do not parse (null if no error)
error_detailstringDetailed error information (null if no error)
message_handlestringSendblue message handle
date_sentstringISO 8601 formatted date string of the date this message was created
date_updatedstringISO 8601 formatted date string of the date this message was last updated
from_numberstringE.164 formatted phone number string of the message dispatcher
numberstringE.164 formatted phone number string of your end-user (not the Sendblue-provided phone number)
to_numberstringE.164 formatted phone number string of the message recipient
was_downgradedbooleanTrue if the end user does not support iMessage, null otherwise
planstringValue of the Sendblue account plan
media_urlstringA CDN link to any media attached to the message
message_typestringType of message (e.g., “message”)
group_idstringGroup identifier (empty string for non-group messages)
participantsarrayArray of participant phone numbers
send_stylestringExpressive message style if used (empty string if none)
opted_outbooleanTrue if the recipient has opted out
sendblue_numberstringThe Sendblue phone number used
servicestringThe messaging service used (e.g., “iMessage”, “SMS”, “RCS”)
group_display_namestringDisplay name for group chats (null for non-group messages)

The status field will report the status. There are currently 8 different possible statuses:

StatusDescription
REGISTEREDThe message has been registered in our system
PENDINGThe message has reached our servers
DECLINEDThe message was rejected by a rule (see error_key for which one, and error_reason for what to change)
QUEUEDThe message has passed validation and has been queued for delivery
ACCEPTEDThe message has been dequeued and is being sent
SENTThe message has been sent (terminal state for SMS, or if the receiver is not connected to the iMessage network)
DELIVEREDThe message has been delivered (terminal state for iMessage or RCS)
ERRORThe message has failed to send, see the error_code field to understand why

Any Code besides 0 or null is a failure. Some codes are not yet documented.

CodeDescription
4000Validation Error: see error_message field
4001Rate Limit Exceeded
4002Blacklisted Number (e.g. 911)
4007Pre-reply limit: content not allowed before the contact’s first reply (PRE_REPLY_CONTENT_BLOCKED). See Before the first reply
4008Pre-reply limit: message longer than 300 characters (PRE_REPLY_LENGTH_EXCEEDED)
4009Pre-reply limit: 6 messages already sent before a first reply (PRE_REPLY_LIMIT_REACHED)
4010Pre-reply limit: today’s override for this contact already used (PRE_REPLY_OVERRIDE_EXHAUSTED)
5000Internal Error
5003Server Rate Exceeded
5509Rate limit window exceeded — reduce send frequency or wait for the current window to reset
10001Message failed to send
10002Failed to resolve message status
SMS_LIMIT_REACHEDSendblue’s internal SMS sending limits exceeded. Wait for the limit window to reset before sending additional SMS messages. See Limits & Queues for details.

Messages sent using Sendblue must be less than 18996 characters in length. If you have larger requirements we recommend breaking the message up into several, smaller messages.

When you provide from_number, Sendblue uses that assigned line and enforces its sending rules. Use GET /api/lines to retrieve the lines on your account.

Automatic capacity-aware selection is available on dedicated and enterprise accounts. An account admin can turn on Automatic API line selection under Settings → API keys → Sending behavior in the Beta Dashboard or Settings → API Settings → Sending behavior in the legacy dashboard. Both dashboards control the same setting. If the option is not shown, contact support to check availability for your account. This setting applies to the entire account and is off by default.

Once enabled, omit from_number from an ordinary direct text or single-media send. New contacts may be sent from different numbers according to available capacity. Explicit from_number choices always take priority. Turning the setting off restores the existing omitted-sender routing for subsequent requests; it does not change queued messages or undo saved contact routes. Accounts without enablement keep their existing omitted-sender routing.

Sendblue chooses from your assigned online lines and preserves an eligible saved conversation line during a temporary outage while the contact is still known on that line. That offline conversation follows the usual availability handling instead of switching its sender. If its known-contact history has expired, selection uses eligible online lines. A line retained during a replacement grace period can only continue contacts already known on that same line; it cannot receive new contacts through automatic selection.

Terminal window
curl -X POST https://api.sendblue.com/api/send-message \
-H "sb-api-key-id: YOUR_API_KEY_ID" \
-H "sb-api-secret-key: YOUR_API_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"number":"+15551234567","content":"Hello from Sendblue"}'

When new-contact pacing applies to your account, Sendblue prefers a line where the contact is already known, preserving the saved route when that line is eligible and the contact is still known there. A contact is known on a particular line when inbound or dispatched outbound activity falls within the configured inactivity window. Being known on one line does not make the contact known on every line. Accounts outside these limits retain their existing route and preferred-line ordering.

For a new contact, Sendblue tries the existing route or preferred assigned online line and moves to another eligible assigned line if its effective hourly or daily new-contact cap is exhausted. Daily overrides and disabled or overridden hourly caps apply. Capacity is reserved during sending, so a usage snapshot does not guarantee a slot. A known contact can stay on a line whose new-contact quota is exhausted, subject to other sending rules, including a line paused with a daily limit of zero. See Limits & Queues.

The accepted response identifies the chosen sender in from_number and sendblue_number. If all eligible lines reach a new-contact cap, the request returns HTTP 400, status: "ERROR", body error_code: 429, and error_key: "LINE_HOURLY_LIMIT_REACHED" or "LINE_DAILY_LIMIT_REACHED". No message is queued. HTTP 429 responses come from request-rate limiting; send-time capacity declines use HTTP 400 even though their body has error_code: 429. If no eligible line is available, the request returns the existing sender-unavailable error. Billing, verification, opt-out, burst and other sending checks can also reject the request; automatic selection does not bypass them or retry their declines on another line.

Explicit from_number choices remain authoritative. Group messages, inline replies, App Cards and carousels use their existing sender requirements and do not use this automatic selection. Free shared-line plans still require a verified contact and an explicit sender. Other plans retain their existing routing behavior. Line-scoped temporary tokens can only select their permitted lines.

Workflow enrollment itself does not reserve sending capacity. This behavior applies when a subsequent supported API send omits the sender. A workflow’s explicit sender choice, including Set From Number, stays authoritative; enrolling a contact does not enable automatic rotation for those sends.

App Cards and inline replies require iMessage and never use SMS fallback. The following behavior applies to ordinary messages.

Sendblue automatically falls back from iMessage to SMS when the recipient does not have iMessage enabled. This happens at no extra cost. You can detect when a message was downgraded by checking:

  • service field: Will be "sms" instead of "iMessage" if the message was sent via SMS.
  • was_downgraded field: Will be true if the end user does not support iMessage.

If a message with a large attachment is downgraded to SMS, Sendblue will attempt to compress the file to fit within the 5 MB SMS limit. If compression fails, the message will fail to send.

To avoid unexpected fallback behavior, you can use the Lookup API to check a recipient’s service type before sending. There is no option to disable SMS downgrade at the system level.

The message handle is used so that you can query the status of each message at a later date. This is especially useful in high volume message_status=QUEUED cases or TIMEOUT cases.

Enrolled inbound-only and selected dedicated accounts may receive an x-rate-limit-suggestion response header on one-to-one iMessage sends. Its value is a JSON string, for example:

{"version":1,"reasons":["line_burst"],"suggested_delay_ms":10000,"expires_at":"2026-10-20T18:01:00Z"}

This is advisory guidance for subsequent sends. It does not change the response status/body and does not instruct you to resend an accepted message. Existing hard limits still apply. The header is absent for ordinary sends, active replies, groups, known SMS/RCS sends, unknown activity and stale or unavailable guidance.

Supported reasons are warm_hourly, cold_hourly, line_hourly, line_burst and new_contacts_daily. Combined waits use the longest delay. Daily exhaustion for a proven new outbound recipient includes new_contacts_remaining: 0 and new_contacts_reset_at; it does not apply to follow-ups or inbound-first replies. expires_at is the advice freshness deadline, not the daily reset or end of the suggested wait. Pacing estimates do not guarantee delivery or per-number inbound capacity. See line usage for observation windows and states.

Guidance uses the selected service at dispatch. Automatic Apple-side fallback after dispatch is established by later message status, not by this header.