Blog
Engineering

Sending a message with no server

Sending a message with no server

TL;DR

With the Offline Protocol React Native SDK, a message reaches its recipient with no server, API key or broker. Calling start() makes the device a mesh node, and sendMessage routes over BLE, WiFi Direct and the internet across up to 8 hops, MLS-encrypted by default.

The smallest useful thing you can build on Offline Protocol is a message that reaches its recipient with no server in the path. It is also the thing that best explains what the platform actually does, because everything else, identity, discovery, coordination, sits underneath that one send. Here is the whole program in the React Native SDK.

// npm install @offline-protocol/mesh-sdk
import { OfflineProtocol, MessagePriority } from '@offline-protocol/mesh-sdk';

const protocol = new OfflineProtocol({ appId: 'my-app', profile: 'user123' });

protocol.on('message_received', (event) =>
  console.log(`From ${event.sender}: ${event.content}`));

await protocol.start();

await protocol.sendMessage({
  recipient: 'off1…', // the peer's address, derived from its identity key
  content: 'Hello!',
  priority: MessagePriority.High,
});

There is no endpoint URL, no API key, no broker to connect to. That absence is the point. Let us walk through what each line sets in motion.

Bringing up DORS transports with start()

protocol.start() brings up DORS, the transport layer, which routes over BLE, WiFi Direct, internet and, when enabled, Reticulum and Nostr, rather than choosing one. It does not open a socket. The Rust core ships inside the binding, so there is no separate native build step to reach that behavior. From this moment the device is a mesh node: it is discovering nearby peers, advertising itself, and standing ready to relay traffic for others.

sendMessage hands the message to DORS, which treats connectivity as a portfolio rather than a binary state. If user456 is one hop away over BLE, the message goes directly. If they are further out, DORS relays across up to 8 hops, with acknowledgment, retry, and deduplication handled for you. If the internet is available and faster for this path, it uses that too. The application code does not change based on which transport carries the bytes, and it does not fail just because one of them is unavailable.

The priority field is the one lever the caller pulls into that decision. High-priority traffic is moved ahead of routine background sync when the local radios are contended, which matters when the same mesh is carrying both a person’s message and a fleet’s telemetry.

Default MLS encryption for messages

Under the hood, sessions are encrypted with MLS (RFC 9420), so every message is protected without the caller opting in. The recipient is named by their identity, not their address. This is why the recipient is 'user456' and not an IP or a device handle: the identity layer resolves who that is, and the session it establishes is the encrypted channel. You are not addressing a machine on a network, you are addressing a person or a service that the mesh knows how to reach.

Receiving messages as events

The message_received handler is a subscription rather than a return value because, on a disconnected mesh, arrival is not synchronous. A message may hop through several relays and arrive seconds later, or after the recipient’s device comes back into range. Modeling receipt as an event keeps the application honest about that. You react when the message lands, rather than blocking on a request that assumes a live path to a server.

Offline Protocol vs server-based messaging SDKs

Most messaging SDKs are thin clients in front of a server that does the real work: routing, fan-out, delivery, presence. Offline Protocol pushes that work onto the devices themselves, so the same four calls keep working when there is no server to reach. The API stays small because the hard parts, multi-transport routing, relaying, encryption, and identity resolution, are handled beneath the one call rather than exposed as configuration.

If you want to build on it, that send is the right place to start. Everything the platform offers is reachable from there.

Frequently asked questions

Do I need a server to send a message with the Offline Protocol SDK?

No. The program has no endpoint URL and no broker to connect to. Offline Protocol pushes the routing, fan-out, delivery, and presence work a server would normally do onto the devices themselves.

How many hops can an Offline Protocol message relay?

Up to 8. If the recipient is further out than one hop, DORS relays the message with acknowledgment, retry, and deduplication handled for you.

What does the priority field in sendMessage do?

High-priority traffic is moved ahead of routine background sync when the local radios are contended, which matters when the same mesh is carrying both a person's message and a fleet's telemetry.

What happens if the recipient is out of range when I send a message?

A message may hop through several relays and arrive seconds later, or after the recipient's device comes back into range. That is why receipt is modeled as a message_received event rather than a return value.

Does my app code change depending on which transport carries the message?

No. The application code does not change based on which transport carries the bytes, and it does not fail just because one of them is unavailable.

Does the React Native SDK need a separate native build step?

No. The Rust core ships inside the binding, so there is no separate native build step to reach the multi-transport behavior.

More posts
Engineering Jul 17, 2026

Proving identity with no authority to ask

Engineering Jul 9, 2026

How DORS failover works