{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreihhx6ds2qrxhqs433fa4kiubg7qqf6h7b6fjmxbv2hfitmcovfnha",
"uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpd35qkeh552"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreic6yh6bo5rqmlzpnaokntnrpz5iuwslsjgd2tupqn464ykzsnsrmu"
},
"mimeType": "image/webp",
"size": 75028
},
"path": "/blooio-messages/how-to-send-imessages-programmatically-rest-api-python-nodejs-3oo6",
"publishedAt": "2026-06-28T03:45:08.000Z",
"site": "https://dev.to",
"tags": [
"imessages",
"ios",
"ai",
"webdev",
"Blooio",
"dashboard",
"n8n",
"GoHighLevel",
"iMessage API overview",
"Send iMessage with Node.js",
"Send iMessage with Python",
"Start free →"
],
"textContent": "If you've ever tried to **send an iMessage programmatically** , you've probably hit the same wall everyone does: Apple has no public iMessage API. There's no `POST /imessage` in the developer docs, no SDK, no OAuth scope. Yet \"blue bubble\" delivery has 3–4× the open rates of SMS, so the demand to **send iMessages from code** — for CRMs, bots, notifications, and outbound — keeps growing.\n\nThis guide covers the realistic options, then walks through actually sending and receiving iMessages over a **REST API** with working **Python** , **Node.js** , and **curl** examples you can paste and run today.\n\n## Why there's no official iMessage API\n\niMessage is a closed, end-to-end-encrypted protocol tied to Apple IDs and Apple hardware. Apple has never shipped a public API to send iMessages, and \"Messages for Business\" is a support-inbox product gated behind an approval process — not a way to send outbound messages from a script.\n\nSo historically, developers reached for hacks:\n\nApproach | Works from a server? | Reliability | Receiving messages | Notes\n---|---|---|---|---\nAppleScript / `osascript` | No — needs a logged-in Mac with Messages open | Brittle | Polling the local SQLite `chat.db` | Mac-only, breaks on macOS updates\nShortcuts automation | No | Brittle | No | Manual, not built for scale\n\"Just use SMS\" (Twilio etc.) | Yes | High | Yes | Green bubbles, no typing indicators/tapbacks/HD media\nHosted iMessage REST API | Yes | High | Yes (webhooks) | What this guide uses\n\nThe AppleScript route is fine for a one-off script on your own Mac. The moment you want to send from a server, send at scale, or _receive_ replies reliably, you need a hosted API that manages the Apple side for you and exposes a normal HTTP interface.\n\n## The setup\n\nFor the examples below I'm using Blooio, an iMessage REST API. Any provider with a similar HTTP surface will follow the same patterns — the concepts (Bearer auth, a send endpoint, webhooks for inbound) are what matter.\n\nYou'll need:\n\n * An API key (Blooio gives you one in the dashboard — no credit card, no A2P/10DLC registration, no DUNS number)\n * A phone number you can test against\n\n\n\nBase URL for all calls:\n\n\n\n https://api.blooio.com/v2/api\n\n\n## Send your first iMessage (curl)\n\nThe whole thing is one authenticated `POST`. The chat is identified by the recipient's phone number (URL-encoded, because of the `+`):\n\n\n\n curl -X POST \\\n \"https://api.blooio.com/v2/api/chats/%2B15551234567/messages\" \\\n -H \"Authorization: Bearer sk_live_your_key_here\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"text\": \"Hello from the command line 👋\"}'\n\n\nResponse:\n\n\n\n { \"message_id\": \"msg_a1b2c3\", \"status\": \"queued\" }\n\n\nThat's it — no Mac, no AppleScript, no polling. If the recipient has iMessage, it lands as a blue bubble.\n\n## Send an iMessage from Python\n\nThe minimal version uses `requests`. Note the `quote(..., safe='')` on the phone number — forgetting to URL-encode the `+` is the #1 cause of silent `400`s.\n\n\n\n import os\n import requests\n from urllib.parse import quote\n\n API_KEY = os.environ[\"BLOOIO_API_KEY\"]\n BASE_URL = \"https://api.blooio.com/v2/api\"\n\n def send_imessage(to: str, text: str) -> dict:\n chat_id = quote(to, safe=\"\")\n res = requests.post(\n f\"{BASE_URL}/chats/{chat_id}/messages\",\n headers={\n \"Authorization\": f\"Bearer {API_KEY}\",\n \"Content-Type\": \"application/json\",\n },\n json={\"text\": text},\n timeout=10,\n )\n res.raise_for_status()\n return res.json()\n\n if __name__ == \"__main__\":\n result = send_imessage(\"+15551234567\", \"Hello from Python!\")\n print(f\"Message {result['message_id']} -> {result['status']}\")\n\n\n\n export BLOOIO_API_KEY=sk_live_your_key_here\n python send.py\n\n\n## Send an iMessage from Node.js\n\nNode 18+ has native `fetch`, so the basic case needs zero dependencies:\n\n\n\n const API_KEY = process.env.BLOOIO_API_KEY;\n const BASE_URL = \"https://api.blooio.com/v2/api\";\n\n async function sendImessage(to, text) {\n const chatId = encodeURIComponent(to);\n const res = await fetch(`${BASE_URL}/chats/${chatId}/messages`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${API_KEY}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ text }),\n });\n\n if (!res.ok) {\n throw new Error(`Blooio ${res.status}: ${await res.text()}`);\n }\n return res.json();\n }\n\n const result = await sendImessage(\"+15551234567\", \"Hello from Node.js!\");\n console.log(`Message ${result.message_id} -> ${result.status}`);\n\n\n## Blue bubble or green? Check capability first\n\nNot every number can receive iMessage. If you're building an iMessage-first flow with SMS fallback, check capability before sending:\n\n\n\n def has_imessage(phone: str) -> bool:\n chat_id = quote(phone, safe=\"\")\n res = requests.get(\n f\"{BASE_URL}/contacts/{chat_id}/capabilities\",\n headers={\"Authorization\": f\"Bearer {API_KEY}\"},\n timeout=10,\n )\n if not res.ok:\n return False\n return bool(res.json().get(\"capabilities\", {}).get(\"imessage\"))\n\n if has_imessage(\"+15551234567\"):\n send_imessage(\"+15551234567\", \"Blue bubbles only ✨\")\n else:\n # fall back to your SMS provider here\n ...\n\n\n## Receiving iMessages (webhooks)\n\nSending is half the story — to build a bot or a two-way CRM thread you need inbound messages. Instead of polling, you register a webhook URL and the API `POST`s events to you. Verify the HMAC signature so you only trust real events:\n\n\n\n import express from \"express\";\n import crypto from \"node:crypto\";\n\n const app = express();\n\n app.post(\n \"/webhooks/blooio\",\n express.raw({ type: \"application/json\" }),\n (req, res) => {\n const signature = req.header(\"x-blooio-signature\") ?? \"\";\n const event = req.header(\"x-blooio-event\") ?? \"\";\n const secret = process.env.BLOOIO_WEBHOOK_SECRET;\n\n const expected = crypto\n .createHmac(\"sha256\", secret)\n .update(req.body)\n .digest(\"hex\");\n\n const ok = crypto.timingSafeEqual(\n Buffer.from(expected),\n Buffer.from(signature),\n );\n if (!ok) return res.sendStatus(401);\n\n const payload = JSON.parse(req.body.toString(\"utf8\"));\n\n if (event === \"message.received\") {\n const { from, text } = payload.data;\n console.log(`${from}: ${text}`);\n // ...reply, route to your CRM, trigger an LLM, etc.\n }\n\n res.sendStatus(200);\n },\n );\n\n app.listen(3001, () => console.log(\"Listening on :3001\"));\n\n\nFor local development, tunnel the endpoint with ngrok and point the webhook at the tunnel URL. Now you have a full send + receive loop — enough to build an **iMessage bot**.\n\n## Automating without writing code\n\nIf you'd rather wire iMessage into existing tools than write a service, the same API drops into no-code/low-code platforms. Blooio has native nodes/actions for n8n, GoHighLevel, HubSpot, and Zapier — so \"new lead in CRM → send iMessage\" is a couple of clicks rather than a deploy.\n\n## Gotchas worth knowing up front\n\n * **URL-encode the phone number.** `+15551234567` must become `%2B15551234567`. This bites everyone once.\n * **No A2P/10DLC registration, DUNS, or campaign approval** with a hosted iMessage API — that paperwork is an SMS/carrier thing. iMessage rides Apple's network.\n * **Use idempotency keys for retries.** Pass an `Idempotency-Key` header derived from your domain entity (e.g. `order-123-shipped`) so a retry never double-sends.\n * **Throttle bulk sends.** Don't fire 1,000 parallel requests — cap concurrency (e.g. `p-limit` in Node, a semaphore in Python) and back off on `429`.\n * **iMessage ≠ guaranteed.** If a number isn't on iMessage, check capability and fall back to SMS rather than assuming a blue bubble.\n\n\n\n## Wrapping up\n\nYou don't need a Mac mini farm or fragile AppleScript to **send iMessages programmatically** anymore. A hosted **iMessage REST API** turns it into a normal HTTP call — `POST` to send, webhooks to receive — that works the same from **Python** , **Node.js** , curl, or your automation tool of choice.\n\nIf you want to try the exact examples above, you can grab a free key (no credit card, no A2P registration) and read the full reference here:\n\n * iMessage API overview\n * Send iMessage with Node.js\n * Send iMessage with Python\n * Start free →\n\n\n\nWhat are you building — a bot, a CRM integration, outbound? Drop it in the comments. 👇",
"title": "How to Send iMessages Programmatically (REST API, Python & Node.js)"
}