DEV Community

Cover image for How to Send iMessages Programmatically (REST API, Python & Node.js)

How to Send iMessages Programmatically (REST API, Python & Node.js)

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.

This 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.

Why there's no official iMessage API

iMessage 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.

So historically, developers reached for hacks:

Approach Works from a server? Reliability Receiving messages Notes
AppleScript / osascript No — needs a logged-in Mac with Messages open Brittle Polling the local SQLite chat.db Mac-only, breaks on macOS updates
Shortcuts automation No Brittle No Manual, not built for scale
"Just use SMS" (Twilio etc.) Yes High Yes Green bubbles, no typing indicators/tapbacks/HD media
Hosted iMessage REST API Yes High Yes (webhooks) What this guide uses

The 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.

The setup

For 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.

You'll need:

  • An API key (Blooio gives you one in the dashboard — no credit card, no A2P/10DLC registration, no DUNS number)
  • A phone number you can test against

Base URL for all calls:

https://api.blooio.com/v2/api
Enter fullscreen mode Exit fullscreen mode

Send your first iMessage (curl)

The whole thing is one authenticated POST. The chat is identified by the recipient's phone number (URL-encoded, because of the +):

curl -X POST \
  "https://api.blooio.com/v2/api/chats/%2B15551234567/messages" \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello from the command line 👋"}'
Enter fullscreen mode Exit fullscreen mode

Response:

{ "message_id": "msg_a1b2c3", "status": "queued" }
Enter fullscreen mode Exit fullscreen mode

That's it — no Mac, no AppleScript, no polling. If the recipient has iMessage, it lands as a blue bubble.

Send an iMessage from Python

The minimal version uses requests. Note the quote(..., safe='') on the phone number — forgetting to URL-encode the + is the #1 cause of silent 400s.

import os
import requests
from urllib.parse import quote

API_KEY = os.environ["BLOOIO_API_KEY"]
BASE_URL = "https://api.blooio.com/v2/api"

def send_imessage(to: str, text: str) -> dict:
    chat_id = quote(to, safe="")
    res = requests.post(
        f"{BASE_URL}/chats/{chat_id}/messages",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json={"text": text},
        timeout=10,
    )
    res.raise_for_status()
    return res.json()

if __name__ == "__main__":
    result = send_imessage("+15551234567", "Hello from Python!")
    print(f"Message {result['message_id']} -> {result['status']}")
Enter fullscreen mode Exit fullscreen mode
export BLOOIO_API_KEY=sk_live_your_key_here
python send.py
Enter fullscreen mode Exit fullscreen mode

Send an iMessage from Node.js

Node 18+ has native fetch, so the basic case needs zero dependencies:

const API_KEY = process.env.BLOOIO_API_KEY;
const BASE_URL = "https://api.blooio.com/v2/api";

async function sendImessage(to, text) {
  const chatId = encodeURIComponent(to);
  const res = await fetch(`${BASE_URL}/chats/${chatId}/messages`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ text }),
  });

  if (!res.ok) {
    throw new Error(`Blooio ${res.status}: ${await res.text()}`);
  }
  return res.json();
}

const result = await sendImessage("+15551234567", "Hello from Node.js!");
console.log(`Message ${result.message_id} -> ${result.status}`);
Enter fullscreen mode Exit fullscreen mode

Blue bubble or green? Check capability first

Not every number can receive iMessage. If you're building an iMessage-first flow with SMS fallback, check capability before sending:

def has_imessage(phone: str) -> bool:
    chat_id = quote(phone, safe="")
    res = requests.get(
        f"{BASE_URL}/contacts/{chat_id}/capabilities",
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=10,
    )
    if not res.ok:
        return False
    return bool(res.json().get("capabilities", {}).get("imessage"))

if has_imessage("+15551234567"):
    send_imessage("+15551234567", "Blue bubbles only ✨")
else:
    # fall back to your SMS provider here
    ...
Enter fullscreen mode Exit fullscreen mode

Receiving iMessages (webhooks)

Sending 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 POSTs events to you. Verify the HMAC signature so you only trust real events:

import express from "express";
import crypto from "node:crypto";

const app = express();

app.post(
  "/webhooks/blooio",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.header("x-blooio-signature") ?? "";
    const event = req.header("x-blooio-event") ?? "";
    const secret = process.env.BLOOIO_WEBHOOK_SECRET;

    const expected = crypto
      .createHmac("sha256", secret)
      .update(req.body)
      .digest("hex");

    const ok = crypto.timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature),
    );
    if (!ok) return res.sendStatus(401);

    const payload = JSON.parse(req.body.toString("utf8"));

    if (event === "message.received") {
      const { from, text } = payload.data;
      console.log(`${from}: ${text}`);
      // ...reply, route to your CRM, trigger an LLM, etc.
    }

    res.sendStatus(200);
  },
);

app.listen(3001, () => console.log("Listening on :3001"));
Enter fullscreen mode Exit fullscreen mode

For 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.

Automating without writing code

If 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.

Gotchas worth knowing up front

  • URL-encode the phone number. +15551234567 must become %2B15551234567. This bites everyone once.
  • 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.
  • 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.
  • 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.
  • iMessage ≠ guaranteed. If a number isn't on iMessage, check capability and fall back to SMS rather than assuming a blue bubble.

Wrapping up

You 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.

If 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:

What are you building — a bot, a CRM integration, outbound? Drop it in the comments. 👇

Top comments (0)