Skip to content
iMessage APIs

For developers

Building on an iMessage API

Apple ships no endpoint, so everything here is a third-party REST API in front of Apple infrastructure. The calls are simple. The parts that decide whether your integration is still working in three months are not, and they are what this page is about.

The mental model

A provider runs real Apple accounts on real Apple hardware and assigns you a phone number. You POST a recipient and some text; a blue bubble arrives. Replies and delivery events come back to you as webhooks.

Everything unusual about the category follows from that. Pricing is per line rather than per message. Daily sending limits are real and low. And a line can degrade without any error appearing in your logs, because the API and the channel are different systems. What is actually behind the number.

Do you need an SDK?

Almost certainly not. These are small REST APIs — send, status, webhook — and a hand-written client is about forty lines with no supply chain attached. If a provider does publish an SDK, check the last release date before depending on it; in this category a package that has been quiet for two years is common and is a warning rather than a sign of stability.

The exception worth taking is an OpenAPI specification. Given one you can generate a typed client, diff it when it changes, and mock the whole API in tests.

Start with four curl commands, not an integration

Send one message to your own phone. Point status_callback at a request bin and watch the delivery events. Reply from the phone and watch the inbound webhook. Send to an Android number and see what the provider does. Twenty minutes, and it tells you more than the marketing site.

Never expose these client-side

In a Next.js app, prefixing a sending credential with NEXT_PUBLIC_ is a two-character mistake that ships it to every visitor. Anyone reading your bundle can then send from your number, at your cost, in your business’s name.

Quickstart

Sending your first message

Field names follow Sendblue's documented shape because theirs is public. Every provider differs in naming and matches in structure — auth header, recipient, body, callback URL.

bash
curl --request POST 'https://api.sendblue.co/api/send-message' \
  --header 'sb-api-key-id: YOUR_SB_API_KEY_ID' \
  --header 'sb-api-secret-key: YOUR_SB_API_SECRET_KEY' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "number": "+19998887777",
    "content": "Your 2pm window is confirmed.",
    "status_callback": "https://acme.co/webhooks/imessage"
  }'

The whole API surface, in one request.

typescript
const response = await fetch("https://api.sendblue.co/api/send-message", {
  method: "POST",
  headers: {
    "sb-api-key-id": process.env.IMESSAGE_KEY_ID!,
    "sb-api-secret-key": process.env.IMESSAGE_SECRET!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    number: "+19998887777",
    content: "Your 2pm window is confirmed.",
    status_callback: "https://acme.co/webhooks/imessage",
  }),
  // Without this, a hung provider hangs your request handler with it.
  signal: AbortSignal.timeout(10_000),
});

if (!response.ok) {
  // 429 and 5xx are worth retrying. A 422 — recipient not on iMessage —
  // will fail identically forever, so route it to your SMS fallback.
  throw new Error(`send failed ${response.status}`);
}

Node 20+. No dependencies — fetch and AbortSignal.timeout are built in.

python
import os, httpx

response = httpx.post(
    "https://api.sendblue.co/api/send-message",
    headers={
        "sb-api-key-id": os.environ["IMESSAGE_KEY_ID"],
        "sb-api-secret-key": os.environ["IMESSAGE_SECRET"],
    },
    json={
        "number": "+19998887777",
        "content": "Your 2pm window is confirmed.",
        "status_callback": "https://acme.co/webhooks/imessage",
    },
    timeout=httpx.Timeout(10.0, connect=5.0),
)

response.raise_for_status()
print(response.json()["message_handle"])

Reuse a module-level httpx.Client in production so the TLS handshake is not paid per send.

What the quickstarts leave out

The four things that decide whether this survives

Retryable versus permanent failures

A 429 will succeed in four seconds. A 422 — recipient not on iMessage — will fail identically forever. Treating them the same wastes your rate limit and never delivers the message. This is the most common defect in these integrations.

Retry logic that works

Webhook verification and idempotency

Your endpoint is public and guessable. Verify the HMAC over the raw bytes before any parser touches them, return 200 before doing work, and make every consequence safe to apply twice — because at-least-once delivery means it will be.

The inbound guide

Pacing and warm-up

A line is one number behaving like a person. Two hundred messages in ninety seconds is not a person. New lines want single-digit sends per minute and a daily cap well under a hundred for the first fortnight.

The warm-up schedule

Watching delivered rate, not uptime

A throttled line usually returns 200 OK. The status page stays green while delivery quietly collapses. Track delivered events as a ratio of sends, alert on a floor, and run an hourly canary to a phone you own.

What to instrument

Documentation

Whose docs you can read before signing up

Documentation posture is the cheapest available proxy for engineering quality, and it varies more here than the pricing does.

Sandbox

Request shapes are documented publicly — you can write a client from the page.

API reference
Sandbox

Reference is published; confirm current field names against their docs before you build.

API reference

Endpoints worth checking for

The features that reveal platform depth

Sending text is table stakes. These are the ones nobody builds first, and their presence tells you how deep the integration goes.

CapabilityWhy it is a signal
Sending and removing a reactionRemoval is a different Apple operation from adding. Support for it is the most reliable tell in the category.
Group threadsCreating, adding a participant, leaving. Substantially harder than one-to-one, and frequently missing.
Typing indicators on demandSent as a real indicator rather than faked with a client-side delay.
Number lookupIs this recipient reachable on iMessage? Saves failed sends and, on metered plans, money.
Message status by handleReconcile state without depending on a webhook you might have missed.
Documented rate limits, in numbers"Generous" is not a limit. You need a figure per minute and per day, and the behaviour when you cross it.

Before you commit

Read these first

There is no official Apple API

Every framework Apple ships keeps a human or a prior customer action in the loop. Knowing exactly which is which saves a week.

The open-source landscape

BlueBubbles, Matrix bridges, chat.db readers — what each is genuinely good at, and where the free path stops.

Every way to send programmatically

AppleScript, self-hosted bridge, third-party API, Apple's own channel — ranked by how long they keep working.

iMessage API vs RCS API

Same feature list, completely different integration. Which population each actually reaches.

Is there a SOC 2 compliant provider?

More than one claims it. What the report covers, what it cannot cover, and the questions to ask.

Building it into a SaaS product

Multi-tenant line allocation, consent you do not own, and a cost structure that can invert your margins.