← / → arrow keys to navigate · F for fullscreen · O for grid view
You want to send an end-to-end encrypted message to someone. Today the typical answer is "use a messaging app" — Signal, WhatsApp, iMessage.
But every one of those:
DMP solves the same problem without any of those.
DNS is everywhere. Every laptop, every phone, every server already speaks it. It's how example.com turns into an IP address.
DMP repurposes DNS TXT records as a place to store signed, encrypted messages.
flowchart LR
A["Alice's CLI"] -- "writes (HTTPS,
token required)" --> Node["a DMP node"]
B["any reader"] -- "reads (DNS,
no auth)" --> Node
sequenceDiagram
participant A as Alice CLI
participant ANode as Alice's home node
(serves mesh.gnu.cl)
participant DNS as DNS (any resolver)
participant B as Bob CLI
A->>ANode: HTTPS POST
slot-N.mb-{hash(bob)}.mesh.gnu.cl
Note over ANode: stored in sqlite
served on UDP 53
B->>DNS: TXT? slot-N.mb-{hash(bob)}.mesh.gnu.cl
DNS->>ANode: forwards
ANode-->>DNS: ciphertext
DNS-->>B: ciphertext
B->>B: decrypt + verify Alice's signature
Both parties share the mesh domain. Alice writes to her own home node only; Bob reads via DNS from the same zone. Each user only ever needs HTTPS to one node — their own.
Each user only needs HTTPS to their own home node. Nothing else.
The current implementation expects Alice and Bob to share mesh_domain. Both their CLIs are configured with the same domain: mesh.gnu.cl, so the mailbox name they each derive matches.
Three current paths to messaging:
| Setup | Works today? |
|---|---|
| Both users register at the same node, share the same mesh domain | ✓ |
| Both users on a federated 3-node cluster (HTTPS anti-entropy syncs between nodes) | ✓ |
| Different home nodes, different mesh domains, no cluster | ✗ |
A future "DNS-only federation" — where Bob's recv walks his pinned contacts' domains and polls each via DNS — would lift the third case without requiring inter-node HTTPS.
Both, but for different things:
| You're doing... | Channel | Network requirements |
|---|---|---|
dnsmesh initsetting up a config |
none yet | nothing — local only |
dnsmesh registergetting a token at a node |
HTTPS | outbound 443 to the node |
dnsmesh identity publishdnsmesh send |
HTTPS | outbound 443 to your home (and recipient's node, for sends) |
dnsmesh identity fetchdnsmesh recv |
DNS | UDP 53 (or 1.1.1.1 / 8.8.8.8 — almost always reachable) |
| Network | Read messages? | Send messages? |
|---|---|---|
| Home / coffee shop wifi | ✓ | ✓ |
| Hotel captive portal (after login) | ✓ | ✓ |
| Corporate firewall, HTTPS allowed, DNS blocked outbound | via DoH | ✓ |
| Network blocking port 53 entirely | use DNS-over-HTTPS at 1.1.1.1 | ✓ |
| Air-gapped (no internet at all) | ✗ | ✗ |
flowchart LR
CLI["dnsmesh CLI
your laptop / phone
(holds private keys)"]
HTTP["HTTP API
port 443"]
DNS["DNS server
UDP port 53"]
Store[("sqlite
store")]
World["any DNS resolver
anywhere"]
CLI -- POST /v1/records --> HTTP
HTTP --> Store
DNS --> Store
World -- TXT query --> DNS
A DMP node is one process. It speaks HTTPS for writes, DNS for reads, and writes/reads the same sqlite file. No separate database, no auth server, no message broker.
sequenceDiagram
participant A as Alice
participant ANode as Alice's home node
participant DNS as DNS (anywhere)
participant B as Bob
A->>ANode: POST /v1/records (HTTPS)
signed identity record
Note right of ANode: stored in sqlite
served on UDP 53
B->>DNS: TXT? alice@her-domain
DNS->>ANode: forwards query
ANode->>DNS: signed record
DNS->>B: signed record
B->>B: verify Ed25519 signature
pin Alice's pubkey
sequenceDiagram
participant Bob as Bob
participant R as 1.1.1.1
(public resolver)
participant DO as DigitalOcean
(owns gnu.cl)
participant Node as dnsmesh.io
(authoritative for mesh.gnu.cl)
Bob->>R: TXT? id-xxx.mesh.gnu.cl
alt cached at resolver
R-->>Bob: cached value
else cache miss
R->>DO: TXT? id-xxx.mesh.gnu.cl
DO-->>R: try dnsmesh.io for this
R->>Node: TXT? id-xxx.mesh.gnu.cl
Node-->>R: signed record
R-->>Bob: signed record
end
Most queries hit a cache. Cold queries walk the chain. Either way, Bob's CLI verifies the signature locally — no trust in resolvers or paths required.
dnsmesh init uses 1.1.1.1 by defaultdns_resolvers:
- 1.1.1.1 # Cloudflare
- 8.8.8.8 # Google (failover)
Privacy-first deploys: dnsmesh init --no-default-resolvers, then point at your own resolver in ~/.dmp/config.yaml.
dig @1.1.1.1 \
alice@her-domain TXT
What everyone else sees. Goes through caching.
dig @her-node.example \
alice@her-domain TXT
Source of truth. No caching. Use this when something looks stale.
sequenceDiagram
participant Bob as Bob
participant R as Resolver
participant Node as Node (down)
Bob->>R: TXT? alice's identity
alt within cache TTL
R-->>Bob: still resolves
else cache expired
R->>Node: TXT? (timeout)
R-->>Bob: no answer
end
Cached reads survive for one TTL window (default 60s). Cold reads start failing as caches expire. Visible outage = one TTL plus a few minutes of negative-cache lag.
sequenceDiagram
participant Alice
participant Node as Node (down)
Alice->>Node: POST /v1/records
Node--xAlice: timeout
Note right of Alice: send fails
no built-in retry
operator handles it
No retry queue. Pending sends do not survive. Records already published are persisted on disk and come back when the node restarts.
| State | Survives? |
|---|---|
| Records already published (sqlite on disk) | ✓ |
| Resolver-cached records (within TTL) | ✓ |
| Pending sends from clients | ✗ |
| Live HTTP requests in flight | ✗ |
| Heartbeat / discovery liveness | ✗ (until restart) |
flowchart LR
Client["sender's CLI"]
Client -- "HTTPS POST
fan to majority" --> A
Client -- "HTTPS POST
fan to majority" --> C
subgraph Cluster
A["node-a"]
B["node-b
(down)"]
C["node-c"]
A <-- "HTTPS
anti-entropy" --> C
end
Three nodes anti-entropy-sync the same record set every ~30s, over HTTPS (/v1/sync/digest + /v1/sync/pull) — never over DNS. Writes from senders fan to a majority of cluster nodes for durability. Reads from the world union across all nodes. When the dead node returns, it pulls everything it missed since its last sync watermark.
dig @node works but my CLI doesn't" — your CLI is using a different resolver. Check dns_resolvers in ~/.dmp/config.yaml.dns_resolvers: ['127.0.0.1'] after running unbound or similar locally.pip install dnsmesh
dnsmesh init alice --endpoint https://dnsmesh.io
dnsmesh register --node dnsmesh.io
dnsmesh identity publish
Getting started · How DMP works (text) · Protocol spec · GitHub