{
  "$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)"
}