Index

iMessage on n8n: Giving No-Code Users Access to the Most Personal Messaging Platform

Most people who want to automate things don't want to write code. They want to connect a trigger to an action, see data flow through a pipeline, and get back to their actual work.

I've been building Photon's iMessage infrastructure — an SDK and server that lets developers programmatically control iMessage. The SDK is powerful, but it requires writing TypeScript. For a lot of the people who'd benefit most from iMessage automation — support teams, small businesses, people wiring up AI agents — that's a dealbreaker.

n8n is an open-source workflow automation platform. Drag nodes onto a canvas, wire them together, done. It has a large ecosystem of community-built integrations. Building an iMessage node for it felt like the right way to bring this capability to a much wider audience.

This post walks through the process of building n8n-nodes-imessage, the problems I ran into along the way, and what it actually unlocks for people who use it.


What This Makes Possible

Before getting into the technical details, here's what matters: what can someone actually do with this?

An n8n user can now drop an iMessage node into any workflow and:

  • Send messages with full iMessage features — effects like confetti and fireworks, replies, subject lines, typing indicators. Not just plain text. The actual iMessage experience, triggered by any event n8n supports.

  • React to messages — tapbacks (love, like, laugh, etc.) driven by workflow logic. An AI agent can read a message, decide on a reaction, and send it.

  • Create and manage polls — interactive iMessage polls, created programmatically. Useful for team coordination, scheduling, gathering quick feedback.

  • Schedule messages — one-time or recurring (hourly, daily, weekly, monthly, yearly). Set it and forget it.

  • Search and retrieve messages — query message history with date ranges, text search, and sort ordering. Feed conversation data into other parts of a workflow.

  • Listen for events in real-time — a webhook trigger that fires on 25 different event types: new messages, typing indicators, group membership changes, FaceTime calls, FindMy location updates. Each event is verified with HMAC-SHA256 before n8n processes it.

  • Check iMessage availability — verify whether a phone number or email supports iMessage before sending.

None of this requires writing code. Someone can wire up an AI model to respond to iMessages, forward messages to Slack, log conversations to a spreadsheet, or trigger a workflow when a specific person texts — all from the n8n canvas.

For AI agent builders specifically, both nodes are marked as usableAsTool: true, which means n8n's built-in AI features can invoke them directly. An agent can decide to send a message, react to something, or create a poll as part of its reasoning loop.


Building It

The structure

An n8n community node is a TypeScript package that exports node classes. The structure is minimal:

credentials/
  PhotonIMessageApi.credentials.ts    → server URL + API key, X-API-Key auth
nodes/
  PhotonIMessage/
    PhotonIMessage.node.ts            → 21 operations across 6 resources
    PhotonIMessageTrigger.node.ts     → webhook trigger, 25 event types

The action node handles messages, chats, polls, scheduled messages, contacts, and handle availability. Each operation maps to a REST endpoint on the Photon server.

The trigger node registers a webhook. When something happens — a new message, someone starts typing, a participant joins a group — the Photon server sends a signed POST request. The node verifies the HMAC signature and passes the event into the workflow:

const expected = createHmac('sha256', signingSecret)
  .update(`${timestamp}.${rawBody}`)
  .digest('hex');

If the signature doesn't match, the event is dropped. Every event that enters a workflow has been cryptographically verified.

Making it feel right in the editor

The initial version worked but felt rough. iMessage has concepts that aren't immediately obvious — Chat GUIDs like iMessage;-;+1234567890, part indexes for multi-part messages, effect names that are internal strings rather than human-readable labels.

I spent time on UX details that make the node feel native:

  • Operations are ordered by how often people use them. Send Message is first. Destructive operations like Unsend are last.
  • Chat GUID fields have inline hints explaining the format.
  • Advanced parameters like Part Index are tucked behind "Additional Fields" so they don't clutter the default view.
  • Reactions show emoji labels — ❤️ Love, 👍 Like, 😂 Laugh — instead of raw enum values.
  • Message effects are grouped into Bubble (Slam, Loud, Gentle, Invisible Ink) and Screen (Confetti, Fireworks, Lasers, Balloons, etc.).
  • Poll options use structured inputs instead of comma-separated strings.
  • The trigger's 25 event types are grouped into categories: Messages, Chat, Media, Connectivity, Server.

Small things, but they're the difference between a node that technically works and one that people actually enjoy using.


Problems Worth Mentioning

n8n's linter is strict and opinionated

The @n8n/node-cli linter enforces rules I didn't expect:

  • Icons must be SVG. I initially tried PNG thinking it would render better. The linter rejected it immediately. SVG only, no exceptions.
  • All option lists must be alphabetized. Every dropdown, every enum, every resource list. If "Chat" comes before "Attachment" in your code, the linter fails.
  • Credential displayName must end with "API." A subtle rule. If your credential is called "iMessage by Photon" instead of "iMessage by Photon API", submission fails.
  • Default limit values must be 50. Not 10, not 100. Fifty.

The right move is to run n8n-node lint before writing any real logic and fix the structural requirements first. Fighting the linter late in the process is painful.

npm audit with zero vulnerabilities still fails

The n8n Creator Portal runs npm audit on your published package. Without a lockfile, it fails:

loadVirtual requires existing shrinkwrap file

The package has zero production dependencies — n8n-workflow is a peer dependency provided by n8n at runtime. But npm audit still needs a lockfile to check. And if you include your dev lockfile, it'll flag transitive dev dependency vulnerabilities that have nothing to do with what ships to users.

The solution: generate a minimal npm-shrinkwrap.json at publish time in CI that reflects reality — zero deps, zero vulns:

const p = require('./package.json');
const s = {
  name: p.name, version: p.version, lockfileVersion: 3,
  requires: true,
  packages: { '': {
    name: p.name, version: p.version, license: p.license,
    engines: p.engines, peerDependencies: p.peerDependencies
  }}
};
require('fs').writeFileSync('npm-shrinkwrap.json',
  JSON.stringify(s, null, 2) + '\n');

npm provenance is required

The n8n verification process requires provenance attestations. Your GitHub Actions workflow needs id-token: write permission and npm publish --provenance. Without it, npm publishes fine but the package silently lacks the attestation n8n checks for.

Running n8n on macOS Apple Silicon

n8n 2.11+ requires Node >= 22.16. On macOS ARM, n8n can crash on startup due to a known issue with isolated-vm and the V8 engine on Apple Silicon. It happens at the process fork level — the library itself loads fine, but the task runner subprocess crashes.

The fix is to run n8n in Docker:

docker run -d --name n8n \
  -p 5678:5678 \
  -e WEBHOOK_URL=https://your-public-url/ \
  -v n8n_data:/home/node/.n8n \
  n8nio/n8n:latest

The WEBHOOK_URL environment variable is important. Without it, n8n registers webhooks at localhost:5678 and external services can't reach them. If you're testing webhooks with a tunnel like ngrok, set this to your tunnel URL and re-activate the workflow — n8n only registers production webhooks when the workflow is toggled on.


What This Means for iMessage Automation

iMessage has always been a walled garden. Apple doesn't provide a public API. Building anything on top of it has historically required deep macOS internals knowledge — SQLite databases, AppleScript, macOS security permissions, binary plist parsing.

Photon abstracts all of that into a REST API. This n8n node abstracts the REST API into visual blocks.

The result is that someone who has never written a line of code can now build an AI agent that lives in iMessage. They can set up a workflow where incoming messages get routed to an LLM, the response gets sent back with a typing indicator and a message effect, and the whole thing runs 24/7 on a webhook trigger.

That's a meaningful change in who can build on top of iMessage.