DEV Community

Royal Simpson Pinto
Royal Simpson Pinto

Posted on

Delivering messages with no internet, no servers, and no SIM

Every messenger you use has a hidden dependency: a working network path to a datacenter. Drop into a basement, a packed stadium, a moving train through a tunnel, an exam hall with jammers, or a remote area with no plan, and the app is just a spinner. The people you want to reach are often standing a few meters away, but your message still has to travel to a server on another continent and back. When that path is gone, so is the app.

Kabootar is my attempt to remove that dependency entirely. It is a messenger with no backend at all. Your phone forms a peer-to-peer mesh with other phones nearby, and messages hop device to device over Bluetooth and Wi-Fi until they reach the recipient. No internet, no servers, no SIM. It is built in Flutter, and the routing core is plain Dart.

The core idea: delay-tolerant networking

The insight that makes this work is refusing to assume the recipient is reachable right now. Normal networking is connection-oriented: open a path end to end, then send. If there is no path, there is no delivery.

Kabootar instead treats the network as a delay-tolerant network (DTN). A message does not need a live end-to-end path at the moment you hit send. It needs a chain of carriers that will exist over time. You hand your message to whoever is nearby. They hold onto it, carry it as they walk around, and pass it along to the next phone they meet. Eventually a carrier bumps into the recipient and the message lands, even if that is minutes later and both you and the recipient have long since walked away.

This is store-and-forward, the same shape as a durable, at-least-once message queue, except the queue is running across a swarm of phones instead of inside a datacenter.

How a message actually travels

The routing strategy is epidemic routing: flooding. When you send a message, it spreads to everyone in range like a rumor. Each device that receives it re-broadcasts it onward, so the message replicates through the crowd, taking every path at once. That redundancy is exactly what makes delivery robust in a network where any single link is unreliable and short-lived.

The whole routing brain lives in one place: a framework-free Dart MeshEngine with zero Flutter, radio, or database imports. Every phone applies the same small set of rules to each envelope it sees:

  1. De-dup by message id. A message can arrive by many paths, but it is acted on exactly once. This is what stops flood storms and routing loops.
  2. Learn from a hello. The contact list is built from whoever comes near, via a handshake.
  3. Deliver if it is for me. Save it, show it, and send back an ack.
  4. Receipt on an ack. When the acknowledgement for one of my sent messages makes it back to me, I flip that message to delivered.
  5. Relay and carry otherwise. Decrement the time-to-live, cache it, and re-flood it onward.
  6. Cap everything. TTL, a max-age, and a cache-size bound keep battery and storage in check so a carrier stays honest.

That is the entire mesh in six rules. Delivery receipts are genuinely end to end: the ack epidemically routes back to the original sender the same way the message went out, so the WhatsApp-style double tick means the message really reached the recipient's device, not just some server.

Because de-dup uses the message id and that seen-set is persisted to SQLite, the system survives restarts. A phone that reboots mid-mesh will not re-flood messages it has already handled. At-least-once delivery becomes effectively exactly-once at the edges.

Bluetooth and Wi-Fi as the transport

Under the engine sits a MeshTransport interface, implemented on top of the platform peer-to-peer stacks (Nearby Connections on Android, Multipeer-style discovery on iOS) through flutter_nearby_connections. Phones continuously advertise and scan, form short-lived peer links over Bluetooth and Wi-Fi, and flush their carried messages whenever a link comes up.

The architecture keeps a clean seam. The UI is Material 3 and binds to a single ChatService, which owns state, the hello handshake, and receipt ticks. ChatService talks to the pure MeshEngine for routing decisions, to SQLite for persistence, and to the transport for the actual radios. Because the engine has no framework or hardware dependencies, its behavior is pinned down by tests that run on a laptop with just the Dart SDK. You can watch store-and-forward-across-time play out (recipient offline, a relay carrying, delivery after the recipient returns, sender eventually learning it was delivered) without touching a phone by running dart run tool/engine_check.dart.

Direct chats and private groups are end-to-end encrypted with X25519, Ed25519, and AES-GCM, with signed messages and a safety code to verify a contact. Relays only ever see ciphertext, which matters a lot when your "relay" is a stranger's phone in a crowd.

One honest limitation

Mesh delivery is only as good as the crowd. Delivery needs a chain of carriers to physically exist between you and the recipient over some window of time. In a dense place, a festival, a protest, a stadium, that chain forms readily and messages move fast. In a sparse setting with few phones running the app, the chain may be long, slow, or never complete. There is no server to fall back on, so if the carriers are not there, the message waits. This is inherent to any mesh, not a bug I can code away, and I would rather state it plainly than pretend proximity networking is magic.

There is also a platform wall worth naming: one Flutter codebase runs on both Android and iOS, but a message cannot currently hop across the OS boundary because the two platforms use different peer radios. v1 meshes within an OS family. Forward secrecy is future work too; today's encryption uses long-term static keys.

Why I built it

I wanted to prove that resilient, private messaging does not require anyone's cloud. No account, no backend, no data leaving the device. It is the kind of infrastructure that keeps working in dead zones precisely because it never depended on infrastructure in the first place.

The routing engine is small, provable, and framework-free, which I think makes it a genuinely fun read if you are into distributed systems. It is open source under MIT.

Repo: https://github.com/royalpinto007/Kabootar

Top comments (0)