Skip to main content

Sending Messages

Use POST /api/core/messages/send to queue outbound messages through the Red Cloud message pipeline.

This route accepts an outbound request into the Red Cloud queue-first messaging flow. It does not synchronously guarantee carrier acceptance or handset delivery.

Canonical example values

  • access_token: rc_token_example
  • numberId: num_abc123
  • messageId: msg_abc123
  • externalReference: order_789
  • phone: +15551234567

Required headers

  • Authorization: Bearer rc_token_example
  • Idempotency-Key: idem_order_789

Idempotency-Key is required.

Why Idempotency-Key matters

Send requests are the highest-risk place to create accidental duplicates. Always generate and persist an idempotency key that is stable for the business operation being performed.

Required clarifications

sendFrom

sendFrom must be a Red Cloud numberId.

It is:

  • a number identifier such as num_abc123
  • not an E.164 phone number

If you pass a phone number instead of numberId, treat that as an integration bug, not as a routing issue.

to[]

Every recipient in to[] must be E.164 formatted.

Example:

  • +15551234567

If you send multiple recipients, evaluate both results[] and errors[]. The route may partially accept the request.

Before you send

Confirm:

  • you already have a valid bearer token
  • you already verified /api/core/whoami
  • the numberId came from /api/core/numbers
  • the chosen number is enabled for the message type you intend to send

Minimal send example

Request

curl -X POST "$BASE_URL/api/core/messages/send" \
-H "Authorization: Bearer rc_token_example" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: idem_order_789" \
-d '{
"sendFrom": "num_abc123",
"to": ["+15551234567"],
"messageType": "SMS",
"text": "Hello from Red Cloud",
"externalReference": "order_789"
}'

Success response

{
"results": [
{
"to": "+15551234567",
"messageId": "msg_abc123",
"status": "QUEUED"
}
],
"errors": []
}

What success means here

QUEUED means the request was accepted by Red Cloud. It does not mean:

  • a provider accepted the message
  • the recipient handset received the message
  • final delivery is complete

Use webhook events for lifecycle truth after the request is queued.

MMS example

Request

curl -X POST "$BASE_URL/api/core/messages/send" \
-H "Authorization: Bearer rc_token_example" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: idem_order_789_mms" \
-d '{
"sendFrom": "num_abc123",
"to": ["+15551234567"],
"messageType": "MMS",
"text": "Catalog preview",
"media": [
{
"storageKey": "storage_abc123",
"mimeType": "image/jpeg",
"byteLength": 182340,
"sourceUrl": "https://example.com/files/catalog.jpg",
"fileName": "catalog.jpg"
}
],
"externalReference": "order_789"
}'

MMS note

The media array must be present for MMS. An SMS send must not include media.

Response interpretation

The route returns:

  • results[] for accepted recipients
  • errors[] for rejected recipients
  • messageId for tracking accepted recipients through webhook lifecycle updates

Partial success example

{
"results": [
{
"to": "+15551234567",
"messageId": "msg_abc123",
"status": "QUEUED"
}
],
"errors": [
{
"to": "invalid",
"error": {
"code": "INVALID_RECIPIENT",
"message": "Recipient must be a valid E.164 phone number",
"details": {}
}
}
]
}

Request error example

{
"error": {
"code": "INVALID_REQUEST",
"message": "Idempotency-Key header is required",
"details": {}
},
"requestId": "req_send_001"
}

Actual implementation rules

  • at least one recipient is required
  • SMS cannot include media
  • MMS must include media
  • at least one of text or media is required

For each accepted recipient:

  1. persist messageId
  2. persist externalReference if you supplied one
  3. correlate future webhook events by messageId
  4. do not mark the message delivered from the synchronous response alone

For each rejected recipient:

  1. inspect the per-recipient error
  2. correct the bad recipient or payload issue
  3. retry only with a deliberate idempotency strategy

Idempotency behavior

  • same Idempotency-Key + same payload:
    • returns the stored 202 response again
  • same Idempotency-Key + different payload:
    • returns 409 CONFLICT

Use idempotency to safely retry client-side sends without duplicating downstream messages.

Common send mistakes

  • using E.164 in sendFrom
  • omitting Idempotency-Key
  • assuming QUEUED means delivered
  • not storing messageId
  • retrying with a changed payload under the same idempotency key

Next step after send

Once sends are accepted, move to:

  • Webhooks for lifecycle handling
  • Errors for request and downstream failure interpretation