All docs

iMessage Detection

Check if a phone number supports iMessage before sending with the Capabilities API.

The Capabilities API lets you check whether a phone number supports iMessage before you send a message. This is useful when you want to customize your messaging logic based on the recipient’s capabilities — for example, sending rich iMessage content to Apple users and a plain SMS fallback to everyone else.

How Detection Works

When you query a phone number’s capabilities, senderZ checks the number against Apple’s iMessage registration system through the iMessage engine on the dedicated Apple hardware. The result tells you whether the number is registered for iMessage.

Results are cached for 90 days. Subsequent queries for the same number within that window return the cached result instantly without hitting the detection backend.

senderZ exposes two endpoints: GET /v1/capabilities/:number for a single phone, and POST /v1/capabilities for batch lookups of up to 100 numbers in one request.


Check a Number

GET /v1/capabilities/:number

Check iMessage capability for a phone number.

number string Required

Phone number in E.164 format (e.g. +15551234567). Passed as a URL path parameter.

curl https://api.senderz.com/v1/capabilities/+15551234567 \
  -H "Authorization: Bearer sz_live_YOUR_KEY"
const number = '+15551234567'
const res = await fetch(
  `https://api.senderz.com/v1/capabilities/${encodeURIComponent(number)}`,
  {
    headers: {
      'Authorization': 'Bearer sz_live_YOUR_KEY',
    },
  }
)
const data = await res.json()

if (data.data.imessage) {
  // Send rich iMessage content
} else {
  // Fall back to SMS
}

Response: iMessage Available

200 OK
{
  "data": {
    "number": "+15551234567",
    "imessage": true,
    "sms": true,
    "cached": false,
    "checked_at": "2026-04-15T10:30:00Z",
    "cache_expires_at": "2026-04-16T10:30:00Z"
  }
}

Response: SMS Only

200 OK
{
  "data": {
    "number": "+15559876543",
    "imessage": false,
    "sms": true,
    "cached": false,
    "checked_at": "2026-04-15T10:31:00Z",
    "cache_expires_at": "2026-04-16T10:31:00Z"
  }
}

GET Response Fields

FieldTypeDescription
numberstringThe phone number that was checked
imessagebooleantrue if the number is registered for iMessage
smsbooleantrue if the number can receive SMS (almost always true for valid numbers)
cachedbooleantrue if this result came from the 90-day cache
checked_atstringISO 8601 timestamp of when the check was performed
cache_expires_atstringISO 8601 timestamp of when the cached result expires

Batch Lookup

Use the batch endpoint when you need to check many numbers at once — for example, when enriching a contact list before a campaign or seeding a routing decision table.

POST /v1/capabilities

Batch capability lookup for up to 100 phone numbers.

phones string[] Required

Array of phone numbers in E.164 format. Minimum 1, maximum 100 entries per request. Duplicates within the array are deduplicated; the response preserves the first-seen order.

curl https://api.senderz.com/v1/capabilities \
  -X POST \
  -H "Authorization: Bearer sz_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phones":["+15551234567","+15559876543"]}'
const res = await fetch('https://api.senderz.com/v1/capabilities', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sz_live_YOUR_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    phones: ['+15551234567', '+15559876543'],
  }),
})
const { results } = await res.json()

for (const r of results) {
  if (r.channel === 'imessage') {
    // Route to iMessage
  } else {
    // Fall back to SMS
  }
}

Batch Response

200 OK
{
  "results": [
    {
      "phone": "+15551234567",
      "channel": "imessage",
      "confidence": "medium",
      "checked_at": 1715000000,
      "ttl_sec": 7776000
    },
    {
      "phone": "+15559876543",
      "channel": "sms",
      "confidence": "low",
      "checked_at": 1715000000,
      "ttl_sec": 7776000
    }
  ]
}

Batch Response Fields

FieldTypeDescription
phonestringThe phone number from the request, in E.164 format
channelstringOne of imessage, rcs, sms, unknown — the recommended channel for this number
confidencestringOne of high, medium, low — how reliable the recommendation is (see below)
checked_atnumberUnix epoch seconds for when the result was produced or last refreshed
ttl_secnumberSeconds the entry remains valid in the cache (currently 7 776 000 = 90 days)

Confidence Levels

LevelMeaning
highReserved for results verified by an actual prior delivery on this tenant. Not currently emitted by the heuristic path.
mediumCached result less than 30 days old.
lowCached result older than 30 days, or a fresh heuristic guess (US numbers default to imessage, all others to sms).

Batch Limits

  • 100 phones per request. Larger lists must be chunked client-side.
  • Sequential calls at 1 request per second comfortably fit the default 60 req/min rate limit. Higher throughput requires a tenant-level rate-limit increase.
  • Capability lookups do not count against your monthly message quota and have no per-call charge.

Caching Behavior

Detection results are stored in the recipient_capabilities table and cached for 90 days. The cache is shared across both the single-number and batch endpoints — a result populated by a GET is served from cache by a subsequent POST, and vice versa.

  1. First query: senderZ checks the number via the iMessage engine (or the heuristic path for batch requests), stores the result, and returns cached: false (or confidence: low for batch).
  2. Subsequent queries within 90 days: senderZ returns the stored result. No additional detection call is made.
  3. After 90 days: The cache entry expires. The next query performs a fresh check.

The 90-day window balances accuracy against performance. A user who switches from Android to iPhone (or vice versa) may not be re-detected for up to three months — but in practice, when an iMessage send fails, senderZ automatically falls back to SMS, so a stale imessage cache entry does not block delivery.

Relationship to channel: “auto”

When you send a message with channel: "auto", senderZ runs the same iMessage detection internally. The key difference:

ApproachWhen to Use
GET /v1/capabilities/:numberSingle-phone routing decision before sending
POST /v1/capabilitiesBatch enrichment of a contact list (up to 100 phones per request)
channel: "auto" on /v1/messagesYou want senderZ to handle routing automatically at send time

All three approaches share the same recipient_capabilities cache and the same 90-day TTL. If you call either Capabilities endpoint and then send with channel: "auto", the send operation will use the cached result from your earlier check.

Error Responses

StatusCodeDescription
400VALIDATION_ERRORInvalid request body — bad E.164, empty array, more than 100 phones, or malformed JSON
401INVALID_API_KEYBad or missing API key
429RATE_LIMIT_EXCEEDEDToo many requests
500SERVER_ERRORCapability lookup failed during cache read — retry with backoff
503DETECTION_UNAVAILABLEiMessage detection backend is temporarily unreachable (single-number GET only)