Introduction

Relayly is a protocol and reference relay for end-to-end encrypted, device-to-device communication over untrusted infrastructure.

Devices authenticate to the relay and pair through a short out-of-band code; the session itself is established directly between the two devices using the Noise Protocol Framework (Noise_XX_25519_ChaChaPoly_BLAKE2s). The relay coordinates pairing and forwards ciphertext, but holds no key material and is cryptographically excluded from every session it carries.

Why Relayly?

Most relay and tunneling tools require you to trust a third-party server with your data, or require accounts and cloud infrastructure. Relayly is different:

  • Zero accounts: devices are identified by cryptographic keys, not emails
  • Zero plaintext: the relay forwards encrypted frames it cannot read
  • Zero cloud: runs on your Raspberry Pi, VPS, or even a local machine
  • Zero lock-in: MIT licensed, portable single binary, five independent official SDKs

When should I use Relayly?

Relayly is ideal for:

  • Local-first applications that need device sync
  • IoT deployments in environments with unreliable internet
  • Privacy-sensitive device communication (health, finance, personal data)
  • Development environments where you want to route between devices without ngrok/cloud
  • Networks with censorship or intermittent connectivity

Protocol & conformance

docs/PROTOCOL.md in the repository is the normative wire spec. Five independent SDKs, Go, TypeScript, Python, Rust, and C++, each implement it from scratch, and a required cross-language CI matrix pairs every one of them against every other, and against itself, before any change can merge. See the Wire Protocol page for a practical summary.

Architecture

relayly/
├── cmd/relayly/      # Main server entry point
├── internal/         # Private server logic (Relay, Database, Admin)
├── sdk/              # Official Client SDKs (Go, TS, Python, Rust, C++)
├── examples/         # Reference implementations
├── docs/             # Protocol spec, RFCs, roadmap
└── Dockerfile        # Production image

Next Steps

  • Quick Start: Get a relay running in under 60 seconds
  • Installation: Docker or binary installation
  • Configuration: Full configuration reference
  • SDKs: Go, TypeScript, Python, Rust, and C++ client libraries

Quick Start for Developers

Start the server and register a device

relayly pair registers a device and prints its device_id and device_token, the credentials an SDK needs to connect. It’s a separate step from pairing two devices with each other, that happens at runtime via the SDK, see below.

docker compose up --build -d
docker exec relayly /relayly pair "My Device"
# ✓ Device ID:    a1b2c3d4-...
# ✓ Device Token: 7f3e9c2a...

Connect with the Go SDK

import relayly "github.com/NIKX-Tech/relayly/sdk/go"

key, _ := relayly.LoadOrGenerateKey("~/.relayly/device.key")

// deviceToken comes from `relayly pair` (or POST /api/v1/devices)
client, _ := relayly.Connect(ctx, "ws://localhost:8080/ws", relayly.Options{
    DeviceID:    "a1b2c3d4-...",
    DeviceToken: deviceToken,
    PrivateKey:  key,
})
defer client.Close()

// Request a pairing code to share with the other device
code, _ := client.RequestPairCode(ctx)
fmt.Println("Share this code:", code.Short)

peer, _ := code.Wait(ctx) // blocks until the other device accepts
client.Send(ctx, peer.ID, []byte("hello!"))
msg := <-client.Messages()

Full reference: pkg.go.dev/github.com/NIKX-Tech/relayly/sdk/go

Connect with the TypeScript SDK

import { RelaylyClient, generateKey } from 'relayly';

// deviceToken comes from `relayly pair` (or POST /api/v1/devices)
const client = new RelaylyClient('ws://localhost:8080/ws', {
  deviceId: 'a1b2c3d4-...',
  deviceToken,
  keyPair: generateKey(),
});

await client.connect();

const code = await client.requestPairCode();
console.log('Share this code:', code.shortCode);

client.on('message', (msg) => console.log(msg.payload));

Full reference: npmjs.com/package/relayly, works in Node.js, browsers, and React Native.

Python, Rust, and C++ SDKs follow the same shape, see the SDKs page for each.


Self-Host in 5 Minutes

Prerequisites

  • Docker (recommended): any version supporting Compose V2, or
  • Go 1.24+: for building the binary directly

Option A: Docker Compose (recommended)

# Clone the repository
git clone https://github.com/NIKX-Tech/relayly.git
cd relayly

# Start in the background
docker compose up --build -d

# Register your first device
docker exec relayly /relayly pair "My Laptop"

The relay will be available at ws://localhost:8080/ws and the admin dashboard at http://localhost:8081 (localhost-only by default).

Option B: Go binary

git clone https://github.com/NIKX-Tech/relayly.git
cd relayly

# Build
go build -o relayly ./cmd/relayly

# Start
./relayly start

Configuration

The server reads config/relayly.yaml on startup, and every key can be overridden with a RELAYLY_* environment variable.

VariableDefaultDescription
RELAYLY_PORT8080WebSocket and API port
RELAYLY_DB_PATHdata/relayly.dbSQLite database file path
RELAYLY_ADMIN_ENABLEDtrueEnable the HTMX admin dashboard
RELAYLY_ADMIN_HOST127.0.0.1Interface the admin UI binds to
RELAYLY_LOG_LEVELinfoLog verbosity (debug, info, warn, error)

Security notes

  • Pairing codes expire in 5 minutes and are single-use. There are no long-lived shared secrets beyond the device token; devices authenticate to each other via cryptographic public keys pinned at first pairing.
  • The admin UI binds to 127.0.0.1 by default. It is not exposed to the network unless you explicitly change RELAYLY_ADMIN_HOST.
  • The relay never sees plaintext. Every device pair runs its own Noise XX handshake and transport encryption directly with each other; the relay forwards the resulting ciphertext without ever holding a session key.