Products

Ships to

Developer guideiMessage API7 min read

iMessage API for Node.js and TypeScript

Send and receive iMessage from Node.js or TypeScript with Flow. Install the SDK, get a test key, stream inbound messages, verify signed webhooks in Express and reply.

The short answer

With Flow, install one Node.js package, get a test key with no account and loop over message.received events. Verify the signed webhook, use an idempotency key and run the same handler on iMessage, WhatsApp and Telegram.

Building on the iMessage API? Build on iMessage with Flow: one agent for iMessage, WhatsApp and Telegram. New to it? Read the iMessage API guide.

Install and get a key

npm install @flow-engineer/messaging
npx @flow-engineer/messaging init

init creates a test key with no account, writes FLOW_MESSAGING_KEY to .env, adds .env to .gitignore, and prints a sandbox link. Open the link on your phone and tap Start. The sandbox runs on Telegram. The handler is the same on iMessage, so build here first. The iMessage API guide explains why Apple has no official API.

A test key allows one contact and 50 messages and expires after seven days. Sign in with a Flow account to keep the app.

The first reply

Save this as agent.ts and run node --env-file=.env agent.ts. It is the example from Flow’s quickstart:

import { FlowMessaging, contentText } from "@flow-engineer/messaging";

const flow = new FlowMessaging();
for await (const event of flow.events.stream({ types: ["message.received"] })) {
  const said = contentText(event.data.message.content);
  await event.conversation.reply("You said: " + said, {
    idempotencyKey: event.id,
  });
}

The stream reconnects and resumes for you, and needs no public URL, so it is the easiest way to develop on a laptop. Replace the echo with your model call. reply() also accepts a model stream and sends complete chat bubbles as they are ready. See iMessage agent with the Vercel AI SDK for a worked example.

Receive events in production

In production, register an HTTPS endpoint with POST /v1/webhook_endpoints and verify every delivery. This is the Express example from Flow’s docs:

import express from "express";
import { FlowMessaging, contentText } from "@flow-engineer/messaging";

const flow = new FlowMessaging();
const app = express();

// The raw body is required: a parsed and re-serialized body will not match.
app.post("/flow", express.raw({ type: "application/json" }), async (req, res) => {
  let event;
  try {
    event = await flow.webhooks.constructEvent(req.body, req.header("Flow-Signature"), process.env.FLOW_MESSAGING_WEBHOOK_SECRET!);
  } catch {
    return res.status(400).end();
  }
  if (event.type === "message.received") {
    return res.json(flow.webhooks.reply(`You said: ${contentText(event.data.message.content)}`));
  }
  res.json({});
});

app.listen(3000);

Answering in the webhook response is the fastest path, and Flow waits 10 seconds for it. For slower work, return {} at once and send the reply later. iMessage webhooks: signing, retries and ordering explains what happens when your endpoint is down.

To try the handler on localhost with no tunnel, run npx @flow-engineer/messaging listen --forward-to http://localhost:3000/flow. It forwards each event from the live stream, signed like a real delivery.

No SDK: use fetch

The API is plain HTTP, so fetch works. This is the reply call from the docs:

await fetch(`https://api.flow.engineer/v1/conversations/${conversationId}/messages`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FLOW_MESSAGING_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": event.id,
  },
  body: JSON.stringify({ content: { type: "text", text: "Hello from my agent" } }),
});

Send iMessage programmatically lists the errors to handle, such as rate_limited and outside_window.

Other languages: Python and Go.

Frequently asked questions

Is there an iMessage API for Node.js?

Yes. Flow publishes @flow-engineer/messaging on npm. The underlying API is HTTP, so plain fetch also works.

How do I get an API key?

With Flow, run npx @flow-engineer/messaging init or call POST /v1/sandbox/keys. You get a test key with no account. It allows one contact and 50 messages on the Telegram sandbox and expires after seven days.

Can I test iMessage from Node.js without an iPhone?

You can test your logic with simulated contacts on a test key, and test on a real phone through the Telegram sandbox. For iMessage itself, Flow has a free shared line for up to 25 people a month.

How do I verify a webhook in Express?

Use express.raw so you keep the raw body, then call flow.webhooks.constructEvent with the body, the Flow-Signature header and your webhook secret. Return 400 if it throws.

How do I avoid sending a reply twice?

Pass the event ID as the idempotency key. Flow keeps keys for 24 hours and returns the first answer for a repeat.

Do I need TypeScript?

No. The SDK works from JavaScript, and the API is plain HTTP. TypeScript gives you typed events and content helpers.

Start hereiMessage API: a developer’s guideThe overview of how iMessage APIs work, and what to ask a provider. Developer guideSend iMessage programmaticallycurl, TypeScript and Python: get a key, receive a signed webhook, reply once and handle retries.Developer guideiMessage API without a MacWhy a hosted iMessage API removes the Mac, what the DIY relay costs you, and how to choose.Developer guideiMessage MCP serverTwo things people mean by it: a messaging MCP for your coding agent, and your product as an MCP server.Developer guideiMessage API for PythonStream inbound events, verify signed webhooks in FastAPI and reply with idempotency keys.Developer guideiMessage API for GoPoll events, reply and verify signed webhooks with the standard library, no SDK needed.Developer guideiMessage webhooks: signing, retries and orderingWhat a reliable webhook does, and how Flow, Sendblue and Linq differ on retries and replay.

All posts: the Flow blog. The Flow docs have the reference.

From reading to building

Your agent, on iMessage and everywhere else.

Flow gives your agent an iMessage line, and the same code answers on WhatsApp and Telegram and works as an MCP server and CLI. See how it works on iMessage.

Build on iMessage with Flow