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:
Sending iMessages
Section titled “Sending iMessages”There are two endpoints for sending messages:
- To send a message to a single phone number, use the /send-message endpoint.
- To send a message to a group, use the /send-group-message endpoint.
Request Parameters
Section titled “Request Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
| number | string | Yes | E.164 formatted phone number of the recipient (e.g., “+19998887777”) |
| from_number | string | Conditional | Your 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. |
| content | string | No | The message content to send |
| media_url | string | No | URL to media file to send (see Sending files) |
| app_card | object | No | App Card identity, state, and layout (see App Cards) |
| send_style | string | No | Expressive message style (see Expressive messages) |
| status_callback | string | No | URL to receive status updates for this message |
| seat_id | string | No | Identifies 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_to | object | No | Inline reply target with message_handle (see Inline replies) |
| pre_reply_override | boolean | No | Let one otherwise-blocked message through to a contact who has not replied yet, once per contact per day (see Before the first reply) |
Sending files
Section titled “Sending files”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:
| Service | Recommended | Maximum |
|---|---|---|
| iMessage | 20 MB | 100 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.
Status Callback
Section titled “Status Callback”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}Message Status Resolution
Section titled “Message Status Resolution”| Callback Body Field | Type | Description |
|---|---|---|
| accountEmail | string | Associated account email |
| content | string | Message content |
| is_outbound | boolean | True if message is sent, false if message is received |
| status | string | The current status of the message |
| error_code | int | Error code (null if no error) |
| error_message | string | The rule’s key when a rule declined the message (same as error_key), otherwise an error description (null if no error) |
| error_key | string | Stable 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_reason | string | Human-readable text for display, do not parse (null if no error) |
| error_detail | string | Detailed error information (null if no error) |
| message_handle | string | Sendblue message handle |
| date_sent | string | ISO 8601 formatted date string of the date this message was created |
| date_updated | string | ISO 8601 formatted date string of the date this message was last updated |
| from_number | string | E.164 formatted phone number string of the message dispatcher |
| number | string | E.164 formatted phone number string of your end-user (not the Sendblue-provided phone number) |
| to_number | string | E.164 formatted phone number string of the message recipient |
| was_downgraded | boolean | True if the end user does not support iMessage, null otherwise |
| plan | string | Value of the Sendblue account plan |
| media_url | string | A CDN link to any media attached to the message |
| message_type | string | Type of message (e.g., “message”) |
| group_id | string | Group identifier (empty string for non-group messages) |
| participants | array | Array of participant phone numbers |
| send_style | string | Expressive message style if used (empty string if none) |
| opted_out | boolean | True if the recipient has opted out |
| sendblue_number | string | The Sendblue phone number used |
| service | string | The messaging service used (e.g., “iMessage”, “SMS”, “RCS”) |
| group_display_name | string | Display name for group chats (null for non-group messages) |
Status
Section titled “Status”The status field will report the status. There are currently 8 different possible statuses:
| Status | Description |
|---|---|
| REGISTERED | The message has been registered in our system |
| PENDING | The message has reached our servers |
| DECLINED | The message was rejected by a rule (see error_key for which one, and error_reason for what to change) |
| QUEUED | The message has passed validation and has been queued for delivery |
| ACCEPTED | The message has been dequeued and is being sent |
| SENT | The message has been sent (terminal state for SMS, or if the receiver is not connected to the iMessage network) |
| DELIVERED | The message has been delivered (terminal state for iMessage or RCS) |
| ERROR | The message has failed to send, see the error_code field to understand why |
Error Codes
Section titled “Error Codes”Any Code besides 0 or null is a failure. Some codes are not yet documented.
| Code | Description |
|---|---|
| 4000 | Validation Error: see error_message field |
| 4001 | Rate Limit Exceeded |
| 4002 | Blacklisted Number (e.g. 911) |
| 4007 | Pre-reply limit: content not allowed before the contact’s first reply (PRE_REPLY_CONTENT_BLOCKED). See Before the first reply |
| 4008 | Pre-reply limit: message longer than 300 characters (PRE_REPLY_LENGTH_EXCEEDED) |
| 4009 | Pre-reply limit: 6 messages already sent before a first reply (PRE_REPLY_LIMIT_REACHED) |
| 4010 | Pre-reply limit: today’s override for this contact already used (PRE_REPLY_OVERRIDE_EXHAUSTED) |
| 5000 | Internal Error |
| 5003 | Server Rate Exceeded |
| 5509 | Rate limit window exceeded — reduce send frequency or wait for the current window to reset |
| 10001 | Message failed to send |
| 10002 | Failed to resolve message status |
| SMS_LIMIT_REACHED | Sendblue’s internal SMS sending limits exceeded. Wait for the limit window to reset before sending additional SMS messages. See Limits & Queues for details. |
Limits
Section titled “Limits”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.
Useful Information
Section titled “Useful Information”From Number
Section titled “From Number”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 line selection
Section titled “Automatic line selection”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.
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.
SMS Fallback
Section titled “SMS Fallback”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:
servicefield: Will be"sms"instead of"iMessage"if the message was sent via SMS.was_downgradedfield: Will betrueif 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.
Handle
Section titled “Handle”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.
Optional pacing suggestions
Section titled “Optional pacing suggestions”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.