bluebubbles-server: forwarding iMessages from a Mac to an Android device
Server for forwarding iMessages to clients within the BlueBubbles App ecosystem
At a glance
- What is it?
- The BlueBubbles server is the TypeScript back end that reads the iMessage chat database on macOS and forwards messages to clients such as the BlueBubbles Android app over socket.io and Firebase Cloud Messaging. It is a monorepo with a UI and a server package, it reads a database that Apple changes between macOS versions, and the newest tagged release is 1.9.9.
- Who is it for?
- Adopt this server if you want iMessage on an Android phone, if you already accept running a Mac as the message host, and if you are willing to read the source when an Apple update changes the database schema. It is the wrong tool if you need a supported messaging platform, if your Mac is a laptop you close, or if you want a server that runs unattended for years without attention.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem: iMessage lives on a Mac, the phone does not
iMessage is tied to an Apple ID and, practically, to a Mac. The chat history is not exposed through a public API, and the database that holds it is a local SQLite file in the user's home directory. If your phone is an Android device, there is no supported way to receive those messages.
BlueBubbles is the ecosystem that addresses that. This repository is the back-end server for the BlueBubbles App, and its stated purpose is to allow you to forward your iMessages to and from an Android device via the BlueBubbles App.
The intended user is therefore an individual, not a platform team. The setup is a Mac that stays on and connected, this server running on it, and a phone running the client. Everything the server does happens on the Mac: reading the database, detecting new messages, and pushing them out.
That architecture sets the limits before any code is written. The server is only as available as the machine it runs on. A MacBook that sleeps stops delivering messages, and the whole design is a single point of failure by construction. The repository layout, with a `macos/` directory at the top level and a `packages/` workspace, reflects that the project is packaged as a Mac application rather than as a portable server binary.
The iMessage chat database read through TypeORM entities
The technical centre of the project is the `iMessage Library` directory, described as containing all of the classes and code needed to communicate with the iMessage Chat database. The README is unusually specific about how that works, and the mechanism has three parts.
The entities in `api/imessage/entity` are the database models. They define the columns and their types, and in doing so they decide which properties are associated with each entity and what can be read from a table. The project uses TypeORM as its decorator library, which is what allows the database to be queried in an object-oriented way rather than through raw SQL strings scattered across the code.
The transformers in `api/imessage/transformers` sit between the database and the rest of the server, converting values on the way in and on the way out. The example given is dates. iMessage stores dates as seconds since 2001, which is not the usual seconds since the epoch, and the date format changed between macOS v10.12 and v10.13. The transformers absorb that difference so the fetching code does not have to think about it. There are also transformers for integers to booleans and for reaction identifiers to strings.
That last example is the clearest statement of the whole design problem. A date epoch change, a type change and an identifier scheme change are all things Apple can do in a point release of macOS, and each one would break a naive reader. The transformer layer is where those changes are absorbed, which means it is also where the accumulated breakage accumulates.
The types in `server/types.ts` complete the picture, holding what fields are required and optional and which keys are required in a request or response, so the contract between client and server is defined in one place.
Listeners that poll the database and emit new messages
Nothing pushes messages to the server. The server watches for them.
The classes in `api/imessage/listeners` are the mechanism. They listen for certain things, and the example is the MessageListener, which listens for new messages by polling the database for new information and then emitting that message to whoever is listening. These classes inherit the JavaScript EventEmitter class, so the rest of the server subscribes to events rather than polling in parallel.
Polling has a direct consequence for behaviour. New message delivery is bounded by the poll interval, not by the speed of the disk or the network. A message that arrives while the machine is asleep is not lost, since the database row is still there when the next poll runs, but it is delayed by however long the machine was off plus one interval. Any user expectation of instant delivery has to be reconciled with that.
The current feature set lists listening for changes in the messages database, covering both new messages and updated messages. Updated messages matter more than they sound: edits, reactions and deletions in the Messages app show up as changes to existing rows, and a listener that only watched for new rows would never see them.
Sending works differently, and the project is direct about it. The `fileSystem/scripts.ts` file holds the Apple Scripts that get executed when sending a message or creating a chat. So the read path is a database query and the write path is script automation of the Messages app. That asymmetry is inherent to the platform: the chat database is readable, but there is no supported way to write a message into it, so the server drives the UI instead.
Socket handlers, the response envelope and FCM
Clients talk to the server over socket.io, and the entry point class in `server/index.ts` manages the ngrok connection, the config database connection, the socket.io connection, and the inter-process communication from the renderer UI.
The socket handlers are a fixed, named list, and the names are worth knowing because they are the actual client-server contract: `add-fcm-device`, `get-chats`, `get-chat-messages`, `get-attachment`, `get-attachment-chunk`, `get-last-chat-message`, `send-message` and `start-chat`. The `get-chats` handler returns all chats with the last message timestamp, and `get-attachment-chunk` is a separate handler from `get-attachment`, which is a detail that only makes sense once you see the next point.
Every response follows one envelope. The format is:
const ResponseFormat = {
status: ValidStatuses;
message: ResponseMessages | string;
error?: Error;
data?: ResponseData;
};The status field is a union of the HTTP-like codes 200, 201, 400, 401, 403, 404 and 500, and the message field is either a fixed enum of response messages or a free string. Optional fields carry the error and the payload. A client therefore never has to guess the shape of a reply, and the feature set also lists an ngrok connection as a way to avoid port forwarding.
Push notifications run through the FCM server in `services/fcm`, which handles all communication with Google Firebase, including registering devices and sending notifications when there are new messages or server updates. The feature set also notes that the FCM database is updated with new server information in case a notification does not reach the device, which is the fallback path when a push is missed. The socket connection is the live channel and FCM is the wake-up call.
Attachment chunking for slow connections
One feature deserves a section of its own because it is the clearest sign of a server built for real networks rather than for a local demo.
The feature set lists attachment chunking to avoid failed downloads on slower connections. The mechanism is visible in the handler list, which contains both `get-attachment` and `get-attachment-chunk` as separate operations, and it exists because a phone on a mobile connection will fail a large transfer that a server on a fast link completes without complaint.
Chunking changes the failure model. Rather than one large response that either completes or times out, the client can fetch a piece at a time and retry the piece that failed, resuming from what it already has. That is why the chunk handler is a first-class operation rather than an internal detail of the attachment handler, and why the request and response types in `server/types.ts` have to define which keys are required for it.
The same reasoning appears in the FCM database update, which covers the case where a notification does not get to the device, and in the attachment by GUID handler, which identifies an attachment independently of any one message.
This is the pattern to watch across the codebase. The project is not trying to make a fast link faster. It is trying to make a slow, intermittent, mobile link reliable enough to receive an image someone sent you on a bus.
Running the monorepo locally with npm workspaces
The project is an npm workspace monorepo. The root `package.json` declares `packages/*` as workspaces and holds the orchestration scripts, while the code lives under `packages/server` and `packages/ui`.
The development path is four steps. Clone the repository, move into it, install the dependencies with npm, and start the dev server:
git clone [email protected]:BlueBubblesApp/BlueBubbles-Server.git
cd BlueBubbles-Server
npm install
npm run startThe `start` script runs both halves at once, using `concurrently` to launch the UI and the server in parallel. The README notes that this starts both the renderer and the server.
Two constraints are stated up front and both bite. The prerequisites are NodeJS and Git. And there is a warning that Yarn may not work for this project, with build errors as the symptom. That is a small but real fact about the build: use npm, and if you see a dependency error, check that a package manager other than npm did not run.
The `devEngines` block pins the expected toolchain precisely: Node 20.11.x, npm 10.x or newer, and Python 3.10.x or newer. The Python requirement is there because the project ships AppleScript and filesystem work rather than only TypeScript.
There is one platform specific wrinkle. On macOS 10.x, the README asks you to downgrade the `node-mac-permissions` dependency to v2.2.0, because a newer version fixed a production crash on Big Sur and later at the cost of macOS 10.x support:
cd packages/server
npm install [email protected]The release scripts follow the same structure, with `build-ui`, `build-server` and `release-server` composed into `build` and `release` targets, and the packaged output collected under `dist/`.
Version 1.9.9, Apache-2.0, and the limits of a database reader
The version story is worth reading closely. The root `package.json` reports version 1.9.9, matching the newest tag, BlueBubbles Server v1.9.9, published on 2025-05-16. Before that came v1.9.8 on 2024-11-20 and a v1.9.8 beta on 2024-07-11. The last push to the default branch was on 2026-10-13, and the repository is not archived.
So the working tree moves while the tags are sparse, roughly four months apart at their closest. If you clone `master` you get code that has not been through a release, which for a server that reads a database Apple keeps changing is a different proposition from a tagged build.
The licence is Apache-2.0, which is permissive, includes an explicit patent grant, and requires preservation of the licence and NOTICE text. The repository carries a `LICENSE` file, `CODE_OF_CONDUCT.md` and `CONTRIBUTING.md`. For personal use of a message bridge this raises no question in practice, and for anyone who plans to modify and redistribute the server, the Apache-2.0 terms are the ones that apply.
Now the honest limits. The server depends on the shape of a private database that Apple owns. The transformers directory exists precisely because macOS 10.13 changed the date format, and there is no guarantee that the next change is covered. A server that stops reading the chat database does not fail loudly, it fails quietly, delivering nothing.
The second limit is the Mac. A MacBook that closes, sleeps or loses network is a silent outage, and the project has no way to make iMessage itself reach a phone. It forwards what the Mac can see. The third is scope: this is a message bridge, not a messaging platform. There is no account system, no federation, no multi-device sync protocol, and no server-side history of its own.
There is a legitimate alternative approach to the same goal, and the difference is architectural. Instead of reading the chat database and driving the Messages app with AppleScript, you can have the server send and receive through the Messages app only, as a one-device-at-a-time bridge with no history. The trade is clean: the database approach gives you history, search and multi-client access, and it inherits the fragility of a private schema. The script-only approach is more durable across macOS updates and much less capable. Which one you want depends on whether you need the history.
Editorial conclusion
Adopt this server if you want iMessage on an Android phone, if you already accept running a Mac as the message host, and if you are willing to read the source when an Apple update changes the database schema. It is the wrong tool if you need a supported messaging platform, if your Mac is a laptop you close, or if you want a server that runs unattended for years without attention. Verify three things first: that the `packages/server` build starts on your macOS version, that the chat database path the server reads matches your iMessage account, and that the socket handlers you depend on, such as `send-message` and `get-chat-messages`, still exist in the version you are running rather than in the documentation you read online.
Frequently asked questions
What does the bluebubbles-server do?
It is the back-end server for the BlueBubbles App, and it forwards iMessages to and from an Android device. It reads the iMessage chat database on macOS, listens for changes to it, and pushes updates to clients over socket.io and Firebase Cloud Messaging.
How do I run the BlueBubbles server locally?
Clone the repository, run `npm install` in the root, then `npm run start`, which launches the UI and the server together through concurrently. The README warns that Yarn may not work and that you may hit build errors.
Which socket.io handlers does bluebubbles-server expose?
The documented handlers are `add-fcm-device`, `get-chats`, `get-chat-messages`, `get-attachment`, `get-attachment-chunk`, `get-last-chat-message`, `send-message` and `start-chat`. All responses use the same envelope with a status, a message and optional error and data fields.
Why does bluebubbles-server need TypeORM and transformers?
The entities under `api/imessage/entity` model the iMessage chat database so it can be queried in an object-oriented way. The transformers convert values on the way in and out, including iMessage dates, which are seconds since 2001, and the format changed between macOS v10.12 and v10.13.
How do I fix build errors on macOS 10.x with bluebubbles-server?
The README asks you to downgrade the `node-mac-permissions` dependency to v2.2.0, by running `cd packages/server` and then `npm install [email protected]`. The newer version was adopted to fix a crash on Big Sur and later.
What licence and version is bluebubbles-server on?
The project is licensed under Apache-2.0, and the root package.json reports version 1.9.9, matching the v1.9.9 release published on 2025-05-16. The last push to the master branch was on 2026-10-13.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bluebubblesapp-bluebubbles-server)Was this useful?