Skip to content
imessageapi

Send your first iMessage from code in 20 minutes

Pick a provider, get a key, send one message to your own phone, and confirm it arrived blue. Nothing else until that works.

6 min readUpdated August 1, 2026Getting started

The fastest way to lose a week on this is to design your whole messaging architecture before you have sent a single message. Do the opposite. Get one bubble onto your own phone, then build outward from something that works.

Step 1 — Pick a hosted provider

For a first send, use a hosted API rather than a self-hosted bridge. You want to isolate one variable — does my code work — not debug a Mac mini at the same time. Sendblue and Miss Blue both get you to a key in a few minutes.

Step 2 — Send to yourself, not a customer

Your own iPhone is the only test recipient you need on day one. You get to see the bubble colour, the link preview, the sender name, and the timing exactly as a customer would.

hello.ts
const res = await fetch("https://api.your-provider.com/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.IMESSAGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "+15550001111", // your own phone
text: "Hello from the build. If this is blue, we are in business.",
}),
});
 
console.log(res.status, await res.json());

If it arrives green

A green bubble means it went out over SMS. Either the recipient is not on iMessage, or your provider fell back. That is not a bug on its own — but if you are testing with an iPhone and still see green, check your provider's routing settings before you build on top of it. Blue vs green explains what changes.

Step 3 — Handle the reply

Outbound-only messaging is a broadcast, not a conversation, and customers will reply whether or not you are listening. Point a webhook at a route handler and log everything before you try to be clever about it.

app/api/inbound/route.ts
export async function POST(request: Request) {
const signature = request.headers.get("x-provider-signature");
const raw = await request.text();
 
if (!verify(raw, signature)) {
return new Response("bad signature", { status: 401 });
}
 
const event = JSON.parse(raw);
console.log("inbound", event.from, event.text);
 
// Always ack fast. Do the real work on a queue.
return Response.json({ ok: true });
}

Verify the signature on day one

Your webhook URL is public. Without signature verification, anyone who finds it can inject fake customer replies into your system. Every serious provider signs its payloads — use it from the first commit, not after the first incident.

Step 4 — Wire STOP before you send anything real

Opt-out handling is not a later feature. The first message you send to a real customer must go out on a system that already honours STOP automatically. The consent guide has the handler.

Once the plumbing works, add UTM parameters to every URL you put in a message. Retrofitting attribution after a campaign has run means the data for that campaign is simply gone. UTM tagging for text messages has a tagging scheme that survives contact with a real team, and the UTM builder will generate one now.

Next step

Generate a tagged link for whatever you send next with the UTM builder, see what this looks like in your industry, or compare the services that can send it on the providers page.