End-to-end encrypted messaging delivered over DNS — Rust SDK and CLI for the DNS Mesh Protocol.
DMP is an open protocol for moving end-to-end encrypted messages between two people using DNS as the transport. The recipient's identity, prekeys, and mailbox slots all resolve like any other DNS record; there is no central server, no app store, and no gatekeeper between sender and recipient. If DNS works on your network, DMP works on your network.
dnsmesh-rs is the Rust port of the client side of that protocol. It
ships:
- A composable client SDK so applications can embed DMP without binding to a single language runtime.
- A command-line interface that doubles as a sendmail-compatible
MTA stub for
mutt/neomuttand other Maildir-aware clients. - A mobile FFI layer for iOS and Android consumers (UniFFI bindings are tracked behind the FFI surface).
The authoritative DNS node, the publish API, and the federation / cluster code live in the Python reference at oscarvalenzuelab/DNSMeshProtocol. This crate consumes the same wire format and is exercised against the reference in CI.
- Embeddability. A single static library that drops into iOS, Android, server backends, and other Rust crates without forcing a Python runtime onto the embedder.
- Footprint. No interpreter, no GIL, predictable memory.
- Deterministic builds.
cargoartifacts are signable and reproducible; mobile teams can pin a specific commit and ship the binary unchanged across stores.
The Rust port is intentionally client-only. The node implementation stays in Python, where its threading model, ops tooling, and federation story already exist.
Pick whichever path fits how you usually install command-line tools.
Pre-built CLI binaries are published on every cli-v<semver> tag at
github.com/oscarvalenzuelab/dnsmesh-rs/releases.
Seven targets ship per release; pick the one matching your machine:
| Platform | Asset |
|---|---|
| macOS — Apple Silicon | dnsmesh-cli-<version>-aarch64-apple-darwin.tar.gz |
| macOS — Intel | dnsmesh-cli-<version>-x86_64-apple-darwin.tar.gz |
| Linux — x86_64, glibc | dnsmesh-cli-<version>-x86_64-unknown-linux-gnu.tar.gz |
| Linux — x86_64, static (musl) | dnsmesh-cli-<version>-x86_64-unknown-linux-musl.tar.gz |
| Linux — aarch64 | dnsmesh-cli-<version>-aarch64-unknown-linux-gnu.tar.gz |
| Windows — x86_64 | dnsmesh-cli-<version>-x86_64-pc-windows-msvc.zip |
| Windows — aarch64 | dnsmesh-cli-<version>-aarch64-pc-windows-msvc.zip |
# macOS Apple Silicon, latest release:
curl -fsSL -o dnsmesh.tar.gz \
https://github.com/oscarvalenzuelab/dnsmesh-rs/releases/latest/download/dnsmesh-cli-aarch64-apple-darwin.tar.gz
tar -xzf dnsmesh.tar.gz
sudo install -m 0755 dnsmesh /usr/local/bin/
dnsmesh --versionmacOS and Windows binaries are code-signed once the signing certs are configured (see CONTRIBUTING.md); until then the workflow ships unsigned binaries with a clear log line.
If you have a Rust toolchain (rustup), the one-liner is:
cargo install --git https://github.com/oscarvalenzuelab/dnsmesh-rs \
--tag cli-v0.1.0 \
--bin dnsmesh \
dnsmesh-cliDrop --tag cli-v0.1.0 to track main instead of a tagged release.
Or, if you'd rather check the tree out and have it around for hacking:
git clone https://github.com/oscarvalenzuelab/dnsmesh-rs
cd dnsmesh-rs
cargo build --workspace --release
sudo install -m 0755 target/release/dnsmesh /usr/local/bin/The release pipeline that produces the prebuilts above runs the same
profile (--release, lto = "thin", codegen-units = 1, strip = true), so building locally gives you the same binary modulo
code-signing.
This walks you through publishing an identity to a public DMP node,
adding a contact, and exchanging a message — about five minutes
end-to-end. It assumes dnsmesh is on your $PATH from one of the
install paths above.
Set a passphrase. This is the only thing protecting your identity
keys — back it up like an SSH key. The CLI reads $DMP_PASSPHRASE
first, then a 0400-permission file, then an interactive prompt.
read -rs DMP_PASSPHRASE # silent — not in shell history
export DMP_PASSPHRASEInitialize, mint credentials against a node, and publish:
# Create local config + keystore.
dnsmesh init alice --domain dmp.example.com --node example.com
# Mint a per-user TSIG key by HTTPS challenge — one round trip.
dnsmesh tsig register --node example.com
# Publish the identity record + a pool of one-time prekeys.
dnsmesh identity publish
dnsmesh identity refresh-prekeysAdd a contact and exchange a message:
dnsmesh identity fetch bob@dmp.example.com --add
dnsmesh send bob@dmp.example.com "hi bob"
dnsmesh recvSanity check that DNS sees what you just published:
dig _dnsmesh-heartbeat.dmp.example.com TXT +shortThe dnsmesh binary speaks just enough of the sendmail interface to
plug into mutt / neomutt as both a sender and a Maildir source.
Minimal ~/.muttrc:
set folder = "$HOME/.dmp/maildir"
set spoolfile = "+inbox"
set sendmail = "/path/to/target/release/dnsmesh send -t"
set use_envelope_from = yes
Then run a polling loop in another shell:
dnsmesh recv --maildir ~/.dmp/maildir --watchA complete walkthrough with neomutt, attachments, supervisor wiring (launchd / systemd / cron), and troubleshooting lives in the mutt integration guide on the docs site.
This is a Cargo workspace. Embedders pull in only what they need.
| Crate | Kind | Purpose |
|---|---|---|
dnsmesh-core |
lib | Protocol primitives, crypto, wire format. |
dnsmesh-net |
lib | DNS transport, resolver glue, publishers (TSIG / Cloudflare / node-token). |
dnsmesh-storage |
lib | SQLite-backed persistence: identity, contacts, prekeys, replay cache, intro queue. |
dnsmesh-client |
lib | High-level DmpClient API composing the lower crates. |
dnsmesh-ffi |
cdylib / staticlib / rlib | C ABI surface for Kotlin / Swift / Python bindings. |
dnsmesh-cli |
bin | The dnsmesh command-line interface + MUA integration. |
cargo fmt --all
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build --workspace --releaseThe release binary lands at target/release/dnsmesh. Cross-compilation
profiles for mobile targets are wired through dnsmesh-ffi's build
matrix (see the FFI crate for aarch64-apple-ios, aarch64-linux-android,
armv7-linux-androideabi).
- Same wire format. Records, manifests, claim records, rotation records, prekeys, and chunked payloads are byte-compatible. Python ↔ Rust round-trip is exercised in CI.
- Client-only. No node, federation, anti-entropy, or operator deploy code lives here.
- SQLite for state instead of the Python reference's filesystem
layout.
dmp-rs.sqliteholds replay cache, contacts, intros, and prekey private bytes. - Mobile-first FFI.
dnsmesh-ffiis a first-class crate, not a bolt-on. - MUA-compat CLI.
dnsmesh send -taccepts the sendmail invocation stylemuttand friends use;dnsmesh recv --maildirdelivers into a Maildir tree.
This Rust port is licensed under the MIT License. This is intentionally asymmetric with the Python reference, which is licensed AGPL-3.0: the SDK is meant to be embeddable by third-party applications (mobile apps, server integrations, other Rust crates) without imposing AGPL obligations on the embedder.
See CONTRIBUTING.md and CODE_OF_CONDUCT.md. In short: format, clippy-clean, tests pass, security-relevant changes get called out in the PR template, and any change touching wire format ships with interop test vectors against the Python reference.