iMessage API: Three Rewrites, One Apple Ban, and What Actually Works

Flo Crivello
CEO
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.
Learn more
Marvin Aziz
Written by
Lindy Drope
Founding GTM at Lindy
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros.
Learn more
Reviewed by
Last updated:
March 18, 2026
Expert Verified

How We Built iMessage Into Lindy

Somewhere in a data center rack in Las Vegas, there used to be a Mac Mini that was the entire iMessage infrastructure for Lindy.ai. It had a paired iPhone. It had an Apple account. It was running a Swift daemon that monitored a SQLite WAL (write-ahead log) file and injected messages through Private Frameworks in ways Apple definitely didn't intend. We built it, rewrote it, and then deleted everything and replaced it with an API call. Three complete rewrites in one month.

Why iMessage is different from every other channel

To send a text message programmatically, you call Twilio's REST API. To send a WhatsApp message, you call Meta's Business API. Telegram, same thing. These are platforms that want developers building on them. They publish docs, hand out API keys, and charge you per message.

Apple does none of this. There is no iMessage Business API. There is no developer program for Messages. If you want to send an iMessage programmatically, you need:

  • A physical Mac (or a Mac in a data center)
  • A physical iPhone paired to that Mac
  • An Apple ID signed into Messages.app on the Mac
  • A daemon that watches the Messages database for incoming messages and injects outgoing ones through undocumented frameworks

That's the starting line. Every company building iMessage integrations starts from the same absurd position.

Version 1: The Swift daemon

In late December 2025, we wrote an RFC for iMessage integration at Lindy. The architecture: a Swift daemon running on a rented Mac Mini that would monitor the Messages.app SQLite database for incoming messages and use Apple's Private Frameworks to send outgoing ones. AppleScript was the fallback.

We set up the machine in mid-January and started building.

The bridge worked. It could send and receive iMessages, handle attachments, and relay everything back to Lindy's API via webhooks. The documentation was thorough.

There was one problem: only one person on the team knew Swift.

The health check endpoint tells the story:

const iMessageBridgeHealthSchema = z.object({
    bridgeId: z.string(),
    status: z.enum(['healthy', 'unhealthy']),
    messagesAppRunning: z.boolean(),
    lastMessageSentAt: z.number().optional(),
    pendingCommands: z.number(),
    uptime: z.number(),
})

messagesAppRunning: z.boolean(). We were literally checking whether Messages.app was still running on a Mac in a data center. pendingCommands tracked how many outbound messages were stuck in the queue. An iMessage bridge runs on physical hardware. When it breaks at 2am, somebody needs to SSH into a Mac Mini and debug a Swift process that's watching SQLite WAL files. If only one person on the team can do that, you have a single point of failure on a critical path.

We started researching what other companies were doing. Several were using an open-source library called BlueBubbles, a JavaScript-based engine for controlling iMessage. Same concept, different language. Our entire team could read and debug JavaScript.

Version 2: BlueBubbles and the bet that didn't hold

We migrated the iMessage bridge to use BlueBubbles, replacing the custom Swift daemon with a JavaScript-based engine running on the same hardware. Same Mac Mini, same iPhone, same Apple ID. But now the bridge code was in a language the whole team could maintain.

The migration unlocked features fast. Within two weeks, we had reactions and tapbacks working, plus markdown formatting support via a custom BlueBubbles build. Edit and unsend support came around the same time.

But the hardware constraint remained. Each iMessage number required its own Mac Mini, its own iPhone, and its own Apple account. We discussed doing what some competitors do: multiple numbers with round-robin assignment, sharing contact cards with users so they wouldn't notice when messages came from a different number.

Too expensive to scale. Ten numbers meant ten Mac Minis, ten iPhones, ten Apple accounts. We decided to ship with a single number and see what happens.

The part nobody planned for

On launch day, thousands of messages went out in the first twelve hours.

The feature worked exactly as intended. Users found iMessage, tried it, and kept using it. An AI assistant you can text like a colleague turns out to be the kind of thing people use a lot. Volume blew past every estimate we had.

Apple's spam detection is a black box. There are no published rate limits for iMessage, no documentation on what triggers a ban, and no appeals process. A new account, high volume, low recipient diversity, and a lopsided send-to-receive ratio (ours was about 4:1) all contribute. The same behavior pattern that looks like a successful product launch looks identical to a spam operation from Apple's perspective.

The account was permanently banned.

The obvious fix was to buy a new iPhone, set up a new Apple ID, provision a new Mac Mini, and start over. That would take days, cost more money, and solve nothing. The next account would get banned too, just slower.

Version 3: The API migration

While we were figuring out next steps, we got connected with a company called Linq. Linq is a managed iMessage bridge service. They handle the hard part (the Mac Minis, the iPhones, the Apple IDs, the number rotation, the anti-spam strategies) and expose a REST API. You call their API, get phone numbers, send messages, receive webhooks. The same developer experience as Twilio or WhatsApp, except for iMessage.

The rebuild took four hours.

The new message sending code:

try {
    return await trySendViaLinq(actor, identity, phoneNumber, message, attachments)
} catch (error) {
    logger.info('Linq send failed, falling back to SMS', { error })
}
return await sendViaSMS(actor, identity, phoneNumber, message)

One function call with an SMS fallback. We had already built the iMessage integration twice. We understood the message routing, the webhook handling, the attachment processing. What changed was we stopped owning the hardware layer and started calling an API. Claude Code helped accelerate the rewrite since the patterns were already clear.

Within a week, the full feature set was back: read receipts, attachments, voice memos, reply-to threading, contact card generation, and a new onboarding flow where users text the system first.

The old bridge code was deleted in a cleanup PR: 76 files changed, thousands of lines removed.

What we learned

Three architectures. Each one was the correct decision given what we knew at the time.

We built the Swift daemon because there was no other option. Apple doesn't provide an API, so you start with the lowest-level tools available. The code worked. The issue was that only one person could maintain it.

We migrated to BlueBubbles because the whole team needed to be able to work on the code. JavaScript over Swift was the right call. The BlueBubbles integration was more capable than the custom daemon: reactions, formatting, edit/unsend. What we couldn't predict was Apple's undocumented spam threshold.

We moved to Linq because after the ban, running our own bridge infrastructure stopped making sense. Linq abstracts the hardware, the accounts, the anti-spam strategy. What's left is a REST API that looks like every other messaging integration we support.

The four-hour rebuild was possible because the two previous rewrites had clarified exactly what the integration needed to do. Each rewrite stripped away complexity until the core was obvious.

Linq now runs with eight phone numbers, load-balanced across users. The anti-spam strategy includes a daily cap per number (Linq's recommendation), a new onboarding flow where users text the system first (so Apple sees reciprocal conversations, not one-way blasts), and dynamic contact card generation so users can save the number.

As for what's still not solved: we're building on a platform that can revoke access at any time, for any reason, with no documentation and no appeals. Linq handles the operational complexity, but the fundamental risk hasn't changed. Apple could change their Terms of Service tomorrow, and every company building iMessage integrations, including Linq itself, would have to adapt or shut down.

The Mac Mini in Las Vegas has been decommissioned. The subscription is cancelled.

About the editorial team
Flo Crivello
Founder and CEO of Lindy

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

Education: Master of Arts/Science, Supinfo International University

Previous Experience: Founded Teamflow, a virtual office, and prior to that used to work as a PM at Uber, where he joined in 2015.

Lindy Drope
Founding GTM at Lindy

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

Education: Master of Arts/Science, Supinfo International University

Previous Experience: Founded Teamflow, a virtual office, and prior to that used to work as a PM at Uber, where he joined in 2015.

Save 2 Hours Every Day
Lindy is your ultimate AI assistant that manages inbox, meetings, and follow-ups—so you stay ahead of the chaos.
Try Lindy for Free
Trusted by 400,000+ professionals

The AI assistant that runs your work life

Lindy saves you two hours a day by proactively managing your inbox, meetings, and calendar, so you can focus on what actually matters.

7-day free trial
Set up in 60 sec