This is the most common form of the question, and it has a clean answer once you know that Apple publishes no API. You are choosing between three routes, and the choice is mostly about who operates the hardware.
- Viable routes
- 3Viable routes
- To first send, hosted
- ~1hrTo first send, hosted
- Cost of the self-hosted route
- $0Cost of the self-hosted route
- Official Apple endpoints
- 0Official Apple endpoints
Route 1 — a hosted provider's REST API
The fastest path, and what most people should use. You POST a phone number and a body; the provider's infrastructure sends the actual iMessage. Every vendor's API is a variation on this shape.
# Illustrative shape. Endpoints and field names differ per vendor —# always check the provider's live docs.curl -X POST https://api.example-provider.com/v1/messages \ -H "Authorization: Bearer $IMESSAGE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "+15551234567", "text": "Your appointment is confirmed for Tuesday at 2pm. Reply STOP to opt out.", "send_style": "default" }'// Write to your own interface from day one. When you change providers —// and in this category you might — this is the only file that moves.type SendResult = { id: string; channel: "imessage" | "sms" }; export async function send(to: string, text: string): Promise<SendResult> { const res = await fetch("https://api.example-provider.com/v1/messages", { method: "POST", headers: { Authorization: `Bearer ${process.env.IMESSAGE_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ to, text }), }); if (!res.ok) { // Treat 429 and 5xx as retryable; 4xx as a bug in your call. throw new Error(`send failed: ${res.status} ${await res.text()}`); } const data = await res.json(); return { id: data.id, channel: data.channel ?? "imessage" };}Wrap the vendor on day one
That send() function is thirty seconds of work and it is the difference between a provider migration taking an afternoon and taking a sprint. Given how young most vendors in this category are, treat it as mandatory. More in switching providers.
Route 2 — a self-hosted bridge
You run a Mac. Software like BlueBubbles watches the local Messages database and exposes a REST API on your own network. No third party holds your Apple ID, no per-line fee, and no vendor to go out of business.
# The bridge runs on your Mac; the API is yours, on your network.curl -X POST "http://your-mac.local:1234/api/v1/message/text" \ -H "Content-Type: application/json" \ -d '{ "password": "'"$BRIDGE_PASSWORD"'", "chatGuid": "iMessage;-;+15551234567", "message": "Your appointment is confirmed for Tuesday at 2pm." }'The trade is straightforward: you swap a monthly bill for an operational responsibility. That Mac must stay awake, stay signed in, stay updated, and stay on a connection. For an internal tool that is nothing. For customer-facing messaging it is a real job.
Route 3 — AppleScript
The floor of the category. Twelve lines, no dependencies, no account, no cost. Also no inbound messages, no delivery confirmation, and it breaks whenever Apple changes Messages.app.
import { execFile } from "node:child_process";import { promisify } from "node:util"; const run = promisify(execFile); export async function sendLocal(phone: string, text: string) { // Passing values as argv keeps quotes and apostrophes from breaking // the script — never string-concatenate user text into AppleScript. const script = ` on run argv set phone to item 1 of argv set msg to item 2 of argv tell application "Messages" set svc to 1st account whose service type = iMessage send msg to participant phone of svc end tell end run`; await run("osascript", ["-e", script, phone, text]);}Which route to pick
| If this is true | Use | Why |
|---|---|---|
| You are messaging customers and it matters | Hosted provider | Delivery signal, inbound webhooks, SMS fallback, someone else's account risk |
| You want blue bubbles with no monthly bill and you own a Mac | Self-hosted bridge | Full control, no vendor, no per-line fee |
| You need a script to text you when a job finishes | AppleScript | Zero setup, and none of the missing features matter |
| You need an officially sanctioned channel | Apple Messages for Business | The only Apple-approved route — grey bubble, inbound-initiated |
Three things that will bite you
- Not every recipient is on iMessage. Roughly half of US mobile users are on Android. Without SMS fallback, those messages silently do not arrive. Confirm fallback behaviour before you launch.
- Links in texts get scrutinised. A bare shortened URL from an unknown number reads as a scam. Use your own domain and tag it — the UTM builder generates the links, and link shorteners and spam filters explains why the shortener matters.
- Consent is not optional. You need it before the first message, and an opt-out path in every campaign. Consent before you text covers what that means in practice.
Language-specific walkthroughs: Node.js and Python. The plain-HTTP framing of the same thing is in is there a third-party POST API. To go end to end from nothing, start at your first iMessage from code.
Related questions
- Can I send an iMessage from a server?
- Not directly through Apple. You send it through a hosted iMessage provider's REST or gRPC API, or through a bridge running on a Mac you control.
- What is the fastest way to send an iMessage from code?
- A hosted provider with published pricing and a sandbox. You can typically get a message delivered within an hour of signing up, with no Mac involved.
Still deciding?
Compare the services that can actually send this on the providers page, see what each one costs, or read the step-by-step guides.