> For the complete documentation index, see [llms.txt](https://docs.bosonnetwork.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bosonnetwork.io/core-concepts/services/messaging.md).

# Photon Messaging

The **Boson Messaging** service (codename **Photon Messaging**) is a federated, end-to-end encrypted instant-messaging system. It runs on a super node, serves client devices over **MQTTS**, and interoperates with messaging services on other super nodes using **REST + CBOR over HTTP/2**.

Think of it as *email-style federation for real-time chat*: every user is registered with one super node - their **home peer** - which routes their messages, queues offline messages, and hosts any channels they own. A user on one home peer can message a user on another; the two home peers discover each other through the Boson DHT and relay sealed message envelopes between themselves. No central server ever sees message content.

This page is the operator- and integrator-facing overview. The implementation-level specifications - message routing, message encryption and contact synchronization - live in the service repository, which is not open source yet; see [Roadmap & Status](/resources/roadmap.md).

***

## Features

* **End-to-end encryption** - message bodies are encrypted by clients before transmission. The home peer routes and stores opaque ciphertext and cannot read user conversation content.
* **Federation like email** - each super node serves its own users and interoperates with others. A user on server A can message a user on server B; server A performs a DHT `FIND_PEER` lookup to locate server B and forwards the sealed envelope over HTTP/2.
* **DHT-based discovery** - the service announces itself as a peer in the DHT, so clients (and peer servers) locate it automatically via `FIND_PEER` with no hard-coded address.
* **Multi-device support** - a user may be signed in on several devices at once. Each device has its own device key and MQTT session, and all of a user's devices observe the same ordered message history through user-level fan-out.
* **Offline delivery** - messages to a disconnected device are queued by the home peer and delivered on reconnect, subject to per-user retention limits.
* **Contact synchronization** - on reconnect a device receives either an incremental **delta** or a full **snapshot** of its contact list, so state converges without re-sending history.
* **Channel support** - group conversations with role-based moderation (`OWNER`, `MODERATOR`, `MEMBER`, `BANNED`) and configurable join permissions. Whether users may create channels, and how many and how large, is a per-user limit.

***

## Architecture overview

```
  Alice (client)                          Bob (client)
       │  MQTTS                                  │  MQTTS
       ▼                                         ▼
 ┌──────────────────────┐   REST + CBOR  ┌──────────────────────┐
 │  Home Peer A         │   over HTTP/2  │  Home Peer B         │
 │  (Photon service)    │ ◄────────────► │  (Photon service)    │
 └──────────────────────┘                └──────────────────────┘
       ▲                                         ▲
       │   DHT FIND_PEER                         │
       └───────────── Boson DHT ─────────────────┘
```

Each user's **home peer** is the super node they are registered with. It:

* terminates the device's MQTTS connection and the transport-encryption layer,
* routes messages to local recipients and forwards to remote home peers,
* queues offline messages and serves them on reconnect,
* hosts the channels its users own and owns those channels' identity keys.

The home peer is **trusted only to route**, never to read. It sees message *headers* (who to deliver to) but only opaque ciphertext for conversation and user-to-user control bodies.

***

## Configuration

The service type is `io.bosonnetwork.photonmessaging`. As shipped:

```yaml
host: 0.0.0.0
port: 9083
ssl: true

federation:
  host: 0.0.0.0
  port: 9084
  ssl: true

peer:
  endpoint: mqtts://node.example.com:9083
  extra:
    federationEndpoint: https://node.example.com:9084

maxMessageSize: 2M
offlineCrossPeerDeliveryLifetime: 7d
evictionInterval: 2h

limits:
  user:
    maxSessions: 2
    inactiveSessionLifetime: 7d
    maxOfflineMessages: 500
    offlineMessageLifetime: 7d
    allowChannel: true
    maxChannels: 2
    maxChannelMembers: 50
```

The MQTTS listener serves client devices; the federation interface serves other super nodes, which find it through `peer.extra.federationEndpoint`. See [Configuring Services](/configure-a-node/services-config.md#photon-messaging) and the [Configuration Reference](/reference/configuration.md#photon-messaging). The federation interface's HTTP API is specified in the [Messaging Federation API](/reference/http-api/messaging-federation.md).

***

## Identity model

All identities are **Ed25519 key pairs**, and the Base58-encoded public key is the stable identifier in every role:

| Role      | Identifier  |
| --------- | ----------- |
| User      | `userId`    |
| Device    | `deviceId`  |
| Channel   | `channelId` |
| Home peer | `peerId`    |

For encryption, Ed25519 keys are converted to Curve25519 and a symmetric key is derived from the resulting shared secret - so the system exposes a single visible key type while using efficient symmetric encryption for payloads.

***

## Message types

Photon defines **four** message categories. The first two carry intent and content; the latter two carry state and key material.

| Type                 | Purpose                                                                                                                                                                        | Scope                                                | Topics       | Notes                                                                                                             |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------- |
| **ContentMessage**   | User-visible conversation content (direct messages, channel messages)                                                                                                          | User-level - fanned out to all of the user's devices | `u/i`, `u/o` | Immutable, persisted, user-visible                                                                                |
| **ControlMessage**   | Intent, negotiation, one-off operations needing explicit consent (friend request/accept/decline, channel create/delete, ownership transfer, permission and membership changes) | Device-level - delivered to a single device          | `d/i`, `d/o` | Point-to-point RPC; end-to-end encrypted; opaque to the home peer unless the peer is the recipient                |
| **StateMessage**     | Explicit synchronization of persistent, user-visible state (contact list, channel membership)                                                                                  | User-level                                           | `u/i`        | Idempotent; represents authoritative state, not intent; delivered to all relevant users - including the initiator |
| **HandshakeMessage** | Cryptographic key exchange and session initialization (establishing session keys between users)                                                                                | User-level - fanned out to all of the user's devices | `u/i`, `u/o` | Routed and delivered exactly like a ContentMessage so every device receives the session-key material              |

{% hint style="info" %}
**RPC vs. state synchronization.** A `ControlMessage` expresses *intent* and is device-scoped; completing one does **not** guarantee that state has converged. Any persistent, user-visible change must be synchronized separately with an explicit `StateMessage` delivered to `u/i`. Never rely on RPC completion for state convergence.
{% endhint %}

***

## MQTT topic hierarchy

Topics are organized along two axes - **scope** (`u` = user-level, `d` = device-level) and **direction** (`i` = inbox, `o` = outbox):

| Topic | Scope  | Direction  | Description                                                                                                |
| ----- | ------ | ---------- | ---------------------------------------------------------------------------------------------------------- |
| `u/i` | User   | Read-only  | Incoming messages and state sync addressed to the user; consumed by all of the user's devices.             |
| `u/o` | User   | Read-only  | User-level outgoing messages, already routed by the home peer and fanned out to all of the user's devices. |
| `d/i` | Device | Read-only  | Messages delivered to one specific device, including RPC (control) responses.                              |
| `d/o` | Device | Write-only | Messages published by a device on behalf of the user.                                                      |

{% hint style="info" %}
**Multi-device view consistency.** Every outgoing message passes through the user-level outbox `u/o`. When a device publishes to `d/o`, the home peer delivers to the recipient *and* republishes to `u/o`; the sender's other devices subscribe to `u/o` and receive their copy - so all of a user's devices observe the same ordered conversation history regardless of which device sent the message.
{% endhint %}

***

## Encryption model

Photon uses **two layers** of encryption. The header stays visible to the home peer (it must route); the body does not.

### Transport encryption (MQTT / peer-to-peer layer)

Every hop - device <-> home peer, and home peer <-> home peer - is encrypted with a symmetric key derived from the two endpoints' encryption keys (e.g. the device encryption key and the home peer encryption key). Neither private key is ever exposed on the wire. The home peer terminates this layer, which is why it can route but the link is still confidential against passive observers.

### Message-body encryption

On top of transport encryption, the message **body** is encrypted again, scoped to its conversation or control context:

| Message                                 | Encryption context (symmetric key derived from...)                                                              | Who can decrypt                                            |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| **ContentMessage** (user <-> user)      | sender encryption key + shared **session** key                                                                  | Both parties                                               |
| **ContentMessage** (user <-> channel)   | sender encryption key + **channel session** key                                                                 | Any channel member holding the session key                 |
| **ControlMessage** (user <-> user)      | sender encryption key + receiver encryption key                                                                 | Only the intended recipient (home peer is an opaque relay) |
| **ControlMessage** (user <-> channel)   | sender encryption key + **channel** key                                                                         | The home peer that hosts the channel                       |
| **ControlMessage** (user <-> home peer) | *body sent in plain* - transport encryption already protects it, and user/device keys belong to the same client | The home peer                                              |
| **StateMessage** (cross-device sync)    | user encryption key + user encryption key (self-context)                                                        | All of the user's own devices                              |
| **StateMessage** (channel)              | channel encryption key + channel session key (authored by the channel-host home peer)                           | Channel members, via user-level topics                     |
| **HandshakeMessage**                    | session-establishment material                                                                                  | The participating users' devices                           |

The home peer sees only opaque ciphertext for ContentMessages, HandshakeMessages, and user-to-user ControlMessages. It processes in cleartext only the control messages explicitly addressed to itself - channel-ownership and channel-management operations, for which it legitimately holds the channel key.

***

## Message routing

Routing is deterministic and depends only on whether the recipient is **local** (same home peer) or **remote** (different home peer).

**Local delivery (same home peer):**

1. Sender's device publishes to `d/o`.
2. The home peer parses the recipient and confirms it is local.
3. It delivers to the recipient's `u/i`.
4. It delivers to the sender's `u/o` (so the sender's other devices see the message).

**Cross-peer federation (different home peers):**

1. Sender's device publishes to `d/o`.
2. Home peer A determines the recipient is remote and performs a DHT `FIND_PEER` lookup for home peer B.
3. Home peer A forwards the sealed message envelope to home peer B over REST + CBOR on HTTP/2.
4. Home peer B applies its **local** routing rules (delivering to the recipient's `u/i`).
5. Home peer A delivers to the sender's `u/o`.

**Channel messages** are resolved by the hosting home peer, which loads the channel members, excludes the sender and banned members, groups members by home peer, delivers to local members via `u/i`, and forwards to remote members' home peers for local routing there.

Federation uses **opaque envelope delivery**: the forwarding home peer does not inspect or modify message content - it only routes the sealed envelope. RPC responses always return to the **originating device** via `d/i`.

***

## Offline message delivery

Messages addressed to a disconnected device are stored by the home peer (up to `maxOfflineMessages` per user) and delivered when the device reconnects. For cross-peer messages whose remote recipient is offline, the **originating** home peer retains the message for the remote peer up to `offlineCrossPeerDeliveryLifetime`.

***

## Contact synchronization

When a device reconnects it presents its **contact revision** during the MQTT `CONNECT` handshake. The home peer compares it to its authoritative revision and sends, before normal message delivery resumes, either:

* a **delta** - the incremental changes since the device's revision, used when the home peer still retains change history back that far (default window: the last 1000 revisions); or
* a **snapshot** - the full contact list, used once the device has fallen behind the retained history window.

***

## Channels

Channels are group conversations hosted on a single home peer. The channel identity is an Ed25519 key pair and the channel ID is its public key. Members hold one of the roles `OWNER`, `MODERATOR`, `MEMBER`, or `BANNED`.

Join permission is either `OWNER_INVITE` (default) or `OPEN`. A channel can optionally be announced to the DHT so new users can discover it. Because the home peer hosts the channel and owns the channel key, channel control messages addressed to the peer are processed by the peer as the channel host, and resulting state changes are propagated to members as `StateMessage`s.

***

## What to read next

* [Build Apps: Messaging Client](/build-applications/services/messaging-client.md)
* [Service Federation](/build-services/service-federation.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.bosonnetwork.io/core-concepts/services/messaging.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
