Back

On this page

  • Why do this at all
  • The shape of it
  • The tools Claude gets
  • Listening for texts
  • Making it feel human, not like a bot
  • Who's allowed to text it
  • Set it up
Back

Claude Code iMessage Channelst

July 15, 2026 · 5 min read
GitHub

An open-source Claude Code channel that connects your Claude Code session to iMessage, running as an MCP server. Text the number and the agent reads it, works the task, and replies, with real iMessage: blue bubbles, tapbacks, typing dots, even a "Claude Code" contact card.

Why do this at all

Claude Code lives in your terminal. That's great at your desk and useless everywhere else. But the agent doesn't really need a terminal. It needs an inbox. If messages can get in and replies can get out, the transport doesn't matter.

iMessage turned out to be a great inbox. Everyone already has it, it's async, it handles photos and files, and the part I leaned on hardest: iOS lets you edit a message after you send it. That last one is perfect for streaming a long task's progress.

So the whole thing comes down to one question. How do you make a coding agent a real iMessage participant?

The shape of it

The bridge is a single MCP server that does two jobs at once.

Going out, it hands Claude a set of tools to speak iMessage: reply, send, react, and a few more.

Coming in, it polls the Linq iMessage API for new texts and pushes them into the Claude Code session.

Linq handles the hard Apple side, a real number that can actually send and receive iMessage. My code is the glue that makes Claude fluent in it.

The tools Claude gets

Claude can only do what its tools let it, so on startup the server registers six of them:

  • reply: answer in a conversation
  • edit_message: rewrite a message it already sent (this is the one that makes streaming work)
  • react: a tapback, like or love or laugh
  • send: start a brand new conversation with a number
  • send_link: drop a URL as a rich preview card
  • check_capability: ask whether a number is on iMessage before sending

Each one is a normal MCP tool. The reply handler, for example, stops the typing indicator, uploads any attachments, builds the message, and sends it, then hands back the message id so Claude can edit that message later.

Listening for texts

The inbound side is a loop that runs every three seconds. Each tick it lists recent chats and figures out what's actually new.

Three small guards do all the work: message ids I've already seen (saved to disk so it survives restarts), a check to skip my own sends, and a timestamp so a fresh session doesn't replay old history. Photos download to a local folder so Claude can open them.

Once a message clears all that, it goes to Claude as a push notification, not a tool result, so the agent reacts to it the moment it lands.

There's also a tiny webhook server as a faster fallback. If Linq can push, we skip the poll wait. If the port is taken, we log it and keep polling. Belt and suspenders.

Making it feel human, not like a bot

This is the part I actually care about. A bot that dumps a wall of text twenty seconds later feels like a bot. The fix is to use iMessage the way a person does.

The big one is streaming. Instead of going quiet and then replying all at once, Claude sends a quick "on it" and then edits that same bubble as it works, a single tool call rewriting the message it already sent. iOS updates it live on your phone, so a long task reads as one message that keeps refining itself, not ten buzzes in a row.

Then the small stuff people expect. The moment a text clears access control, the server marks it read and starts the typing dots, so you see "..." while Claude thinks. Tapbacks, screen effects, bold text, rich link cards, and a contact card so the thread shows up as "Claude Code" instead of a bare number.

Half of "make it feel human" was honestly just the instructions I gave the model:

plain text
Reply with the reply tool, passing the chat_id from the message.
For longer tasks, send a short message first, then edit it as you work.
Do NOT use markdown, iMessage doesn't render it.
Be concise, this is iMessage, not email.

That last line does a lot of work.

Who's allowed to text it

A public phone number is a public front door. Anyone who knows it can text straight into your session, so access control had to be real. There are four modes, and the default is pairing: a stranger gets a short code back and their message is dropped until you approve them from your session.

All of it lives in a plain JSON file that the server re-reads on every incoming message, so approving someone takes effect right away, no restart.

Set it up

Want to run it yourself? It's quick.

First, get a Linq number. The channel talks to iMessage through the Linq API, so you need a token and a number. Fastest is a free sandbox (3-hour expiry, GitHub auth):

shell
curl -fsSL https://raw.githubusercontent.com/linq-team/linq-cli/main/install.sh | sh
linq signup      # GitHub auth, provisions a sandbox number
linq profile     # your token and phone number

Then install the plugin from a Claude Code session and hand it your credentials:

plain text
/plugin marketplace add linq-team/claude-code-imessage-channel
/plugin install imessage@linq
/imessage:configure <your-linq-token>
/imessage:configure <your-linq-phone-number>

Relaunch with the channel flag (it won't connect without it), from any directory except the plugin repo:

shell
claude --dangerously-load-development-channels plugin:imessage@linq

Finally, pair your phone: text the Linq number, get a 6-character code back, and run /imessage:access pair <code>. Your next text reaches the agent. Once you're in, lock it down with /imessage:access policy allowlist.

EmailGitHub
@hvaptl