> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blujay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# iMessage, RCS or SMS

> What works on each service, how to check which service a phone number gets, and when to skip the check and send with Auto.

Blujay sends each direct message over iMessage, RCS or SMS. You usually don't need to decide: leave out `preferred_service` and Blujay picks for you (Auto). This page shows what each service can carry, how to check a number in advance when your app needs to know, and when the check isn't worth it.

## What works on each service

| Feature | iMessage | RCS | SMS |
| - | - | - | - |
| Text | ✅ | ✅ | ✅ |
| Links (after the contact replies) | ✅ with a link preview | ✅ preview depends on the recipient's phone | ✅ preview depends on the recipient's phone |
| Photos and videos | ✅ | ✅ | ✅ up to 5 MiB per message (MMS) |
| PDFs and other files | ✅ | ❌ | ❌ |
| [Contact cards](/messaging/contact-cards.md) | ✅ | ✅ | ✅ |
| Voice memos | ❌ Sending isn't supported: an audio file sends as an ordinary media file. Voice memos you receive arrive as audio media. | ❌ | ❌ |
| [Effects](/messaging/effects.md) | ✅ | Text sent, effect dropped | Text sent, effect dropped |
| [Text styles](/messaging/effects.md) (direct chats) | ✅ | ❌ | ❌ |
| [Threaded replies](/messaging/replies.md) | ✅ | ❌ | ❌ |
| [Edit and unsend](/messaging/edit-and-unsend.md) | ✅ | ❌ | ❌ |
| Reactions (tapbacks) | Receive only, as `reaction.added` and `reaction.removed` webhooks. Sending one returns `400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled). | ❌ | ❌ |
| [Typing indicators](/messaging/typing-and-read-receipts.md), sent and received | ✅ | ❌ | ❌ |
| [Read receipts you send](/messaging/typing-and-read-receipts.md) (direct chats) | ✅ | ✅ | ❌ |
| [Group changes](/messaging/group-chats.md): add, remove, rename, leave | ✅ | ❌ | ❌ |
| Group icon | ❌ Not supported (`400` [`FEATURE_NOT_ENABLED`](/errors/feature-not-enabled)) | ❌ | ❌ |
| `delivered` status | ✅ | When reported | ❌ |
| `read` status | ✅ when the recipient has read receipts on | When reported | ❌ |

Links, media and contact cards are all rejected until the contact replies, whatever the service. See [Sending rules](/messaging/sending-rules.md).

## Check a phone number

When your app needs the service in advance, for example to show the recipient's service in your interface or to keep large media out of an SMS, check the number with `POST /v1/capability/evaluate_service`. Send the US number you want to check and the Blujay phone number you'll send from:

```bash theme={null}
curl https://api.blujay.io/v1/capability/evaluate_service \
  -H "Authorization: Bearer blu_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "number": "+15555550123",
    "from_phone_number_id": "phone_abc123"
  }'
```

```json theme={null}
{
  "status": "success",
  "data": {
    "number": "+15555550123",
    "service": "iMessage",
    "checked_at": "2026-10-14T17:00:00.000Z",
    "source": "fresh"
  },
  "trace_id": "trc_8f2a1c0e4b7d",
  "timestamp": "2026-10-14T17:00:00.000Z"
}
```

| Field | Meaning |
| - | - |
| `service` | `iMessage`, `RCS` or `SMS`. |
| `source` | `fresh` when this request ran a check (or joined one already running), `cached` when Blujay reused a recent answer. |
| `checked_at` | When the check ran. For a cached answer, it's earlier than your request. |

Always send `from_phone_number_id`. Find your numbers' IDs with [`GET /v1/phone-numbers`](/phone-numbers). You can leave it out only when your workspace has exactly one active number; with several, the request returns `400` [`PHONE_NUMBER_REQUIRED`](/errors/phone-number-required). An unknown or inactive number returns `404` [`NOT_FOUND`](/errors/not-found), and a number that isn't a valid US phone number returns `400` [`VALIDATION_ERROR`](/errors/validation-error).

The check counts toward your read request limit, not your write limit; see [Rate limits](/rate-limits.md).

## Read an `SMS` answer carefully

`iMessage` and `RCS` answers are confirmed. `SMS` means one of two things:

* the number is confirmed to use SMS, or
* Blujay couldn't confirm iMessage or RCS this time.

The response doesn't say which. If an `SMS` answer matters, check again later: an inconclusive answer is only reused for 30 seconds, so a later check runs fresh.

## Cached answers are free

When Blujay recently checked the same number from the same Blujay phone number, you get that answer back with `source: cached`. It doesn't matter whether the earlier check came from this endpoint, from your team in the Blujay app, or from a send.

| Answer | Reused for |
| - | - |
| Confirmed (`iMessage`, `RCS`, or confirmed `SMS`) | 5 minutes |
| Inconclusive (`SMS` because Blujay couldn't confirm) | 30 seconds |

Cached answers are free and never limited.

## Fresh check limits

A fresh check runs from your Blujay phone number and shares its capacity with your messages, which always go first. Each of your numbers can run:

* **30 fresh checks per clock hour**, and
* **100 fresh checks per UTC day**.

A fresh check counts as soon as it starts, even if it ends inconclusive. Cached answers don't count.

When a number can't run another fresh check yet, the call returns `429` [`RATE_LIMIT_EXCEEDED`](/errors/rate-limit-exceeded) with a `Retry-After` header. `error.details[0].limit` tells you why:

| `limit` | Meaning |
| - | - |
| `line_capacity` | The number is busy with messages or your recent checks. |
| `line_hourly_quota` | The number has run 30 fresh checks this clock hour. |
| `line_daily_quota` | The number has run 100 fresh checks this UTC day. |

```json theme={null}
{
  "status": "error",
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "This phone number cannot run another capability check yet",
    "type": "rate_limit",
    "http_status": 429,
    "retryable": true,
    "doc_url": "https://docs.blujay.io/errors/rate-limit-exceeded",
    "details": [{ "limit": "line_hourly_quota" }],
    "retry_after": 1260
  },
  "trace_id": "trc_6d2a9c41e8b3",
  "timestamp": "2026-10-14T17:39:00.000Z"
}
```

Wait `Retry-After` seconds before checking from that number again. If checks are briefly unavailable, you get `503` [`CAPABILITY_CHECK_UNAVAILABLE`](/errors/capability-check-unavailable), also with `Retry-After`.

## When to send with Auto

You don't need to check a number before you send to it. With Auto, Blujay works out the service when it sends, and sending never uses your capability-check limits.

| You want to | Do this |
| - | - |
| Send a message and let Blujay pick the best service | Send with Auto (leave out `preferred_service`). Skip the check. |
| Show the recipient's service in your app before anyone sends | Check, and cache the answer on your side. |
| Change the content by service, for example a smaller file for SMS | Check, then send. |
| Send only if the recipient has iMessage | Send with `preferred_service: "iMessage"`. The message fails instead of going out another way, so you don't need a separate check. |
| Keep a message off iMessage | Send with `preferred_service: "RCS"` or `"SMS"`. |

Don't check every number before every send, especially for bulk sends: you'll spend your fresh checks for nothing. The service that actually carried a message is always in its `service` field afterwards; see [Track a message](/messaging/lifecycle.md).

> [!WARNING]
>
> **Common mistakes**
>
> * Leaving out `from_phone_number_id` with more than one number returns `400` [`PHONE_NUMBER_REQUIRED`](/errors/phone-number-required). Pick the number to check from.
> * An `SMS` answer may be inconclusive. Don't treat it as proof the person has no iMessage.
> * `error.details[0].limit` on this endpoint names the quota (`line_hourly_quota`). On sends, the same error uses `details[0].limit_type`.
> * Group chats have no `preferred_service`. Blujay picks for the whole group, and the chat's `service` reports what the group runs on (`iMessage` or `SMS`).

## Limitations

* The check covers direct chats only. A group chat doesn't accept `preferred_service` (`400` [`VALIDATION_ERROR`](/errors/validation-error)), and a group's `service` on `GET /v1/chats/{chat_id}` reports the group's service rather than a per-recipient check.
* An `SMS` answer can mean "not confirmed". There is no field that says which.
* 30 fresh checks per clock hour and 100 per UTC day per number (`429` [`RATE_LIMIT_EXCEEDED`](/errors/rate-limit-exceeded)). Cached answers are unlimited.
* The check needs an active number. An inactive or unknown `from_phone_number_id` returns `404` [`NOT_FOUND`](/errors/not-found).

## Next steps

> **[Send messages](/messaging/send-messages.md)**
>
> Send with Auto or a preferred service.

> **[Track a message](/messaging/lifecycle.md)**
>
> See which service carried each message.

> **[Phone numbers and limits](/phone-numbers.md)**
>
> Find your numbers and their IDs.

> **[Rate limits](/rate-limits.md)**
>
> Request limits and how to back off.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.