Your own mailbox relay
Last updated: 3 October 2026
Herkos mail travels as sealed envelopes (NIP-59 gift wraps) and waits on the relays in your DM relay list until your app collects it. Most public relays hand those envelopes to anyone who asks. Nobody can open them, but anyone can count who receives mail and when, and the relay can delete them whenever it likes.
A relay of your own that asks who is reading (NIP-42) gives your envelopes to you and to nobody else, and keeps them for as long as you decide. This page explains how to set one up in three steps, plus a nightly backup. It takes about fifteen minutes if you already have a server.
Which relay to use
We ran both candidates on 24 September 2026 and checked what they actually do with mail, not what they advertise.
| nogringo/nostr-relay | HAVEN 1.2.2 | |
|---|---|---|
| Who can read your envelopes | Only you, after identifying | /chat: anyone in your web of trust, after identifying. /inbox: anyone, with no identification |
| Who can deliver mail to you | Any sender who identifies | Only your web of trust |
| Deleting mail you received | Yes | No |
| Fast resync (NIP-77) | Yes | No |
| Licence | MIT | MIT |
Use nogringo/nostr-relay as your mailbox. It is the one that does what mail needs: anyone can write to you, and only you can read. HAVEN is a good tool for other things (a personal outbox, media, private notes), but as a mailbox it would turn away mail from anyone you do not follow, including the bridge that carries ordinary email.
What you need
- A machine that is always on and reachable from the internet: a small VPS (about 4-6 € a month) or a computer at home.
- A domain or subdomain pointing to it, for example
relay.example.com. - Docker and a reverse proxy that provides HTTPS. The examples use Caddy, which gets the certificate on its own.
Step 1: run the relay
git clone https://github.com/nogringo/nostr-relay
cd nostr-relay
cp .env.example .env
Edit .env: give the relay a name (RELAY_NAME) and set RELAY_URLS to its
public address, wss://relay.example.com.
Then open docker-compose.yml and change the port line to
"127.0.0.1:3334:3334". The relay trusts the address the proxy forwards to it
when it checks who is identifying, so it must only be reachable through the
proxy. Start it:
docker compose up -d
Step 2: put HTTPS in front
With Caddy, the whole configuration (/etc/caddy/Caddyfile) is:
relay.example.com {
reverse_proxy 127.0.0.1:3334
}
Reload Caddy (sudo systemctl reload caddy). Opening
https://relay.example.com in a browser should now answer.
Step 3: tell Herkos
- In Herkos, open Settings → Network and servers → Your own relay and tap
Check a relay. Enter
wss://relay.example.com. - Herkos connects and tries it: it asks for someone else's mail, and it leaves a test envelope from a stranger (then deletes it). You should see Only you can read your mail here and Accepts mail from any sender.
- Tap Make it your first DM relay, and Save at the top.
Keep a public relay in the list as well, second: if your server is down, mail
still has somewhere to wait. relay.damus.io is a good second choice, because it
also asks who is reading.
Step 4: back it up every night
Herkos keeps its own copy of the mail it has collected, but an envelope that arrived while your app was closed exists only on your server until you open Herkos. A nightly copy covers that gap, and it lets you rebuild the server if the disk dies or the provider closes your account.
The relay keeps everything in one LMDB database inside the Docker volume
nostr-relay_relay-db (the name starts with the folder you cloned into; check
it with docker volume ls). An LMDB file copied while the relay is writing to
it may not open, so the script stops the relay for the few seconds a local copy
takes, starts it again, and only then uploads the copy. While it is stopped,
mail waits on the public relay you kept second in your list.
The copy goes off the server with restic, which encrypts it before it leaves. Use a place at a different provider from the server: an SFTP account (a storage box, a computer at home) or an S3-compatible bucket.
-
Install the tools and create the repository. Keep the password in your password manager as well: without it the copies cannot be read, and if it only lives on the server, it dies with the server. For SFTP, root also needs an SSH key that the backup machine accepts (
sudo ssh-keygen -t ed25519, then add/root/.ssh/id_ed25519.pubthere).sudo apt install restic rsync sudo sh -c 'openssl rand -base64 32 > /root/.restic-password' sudo sh -c 'cat > /root/.restic-env' <<'END' export RESTIC_REPOSITORY=sftp:backup@backup.example.com:/relay export RESTIC_PASSWORD_FILE=/root/.restic-password END sudo chmod 600 /root/.restic-password /root/.restic-env sudo sh -c '. /root/.restic-env && restic init' -
Save this as
/usr/local/bin/backup-relayand make it executable (sudo chmod 700 /usr/local/bin/backup-relay). ChangeRELAY_DIRif you did not clone into/root/nostr-relay. It keeps the last 14 days, 8 weeks and 12 months: a year back, at a size close to that of the database.#!/bin/sh set -eu . /root/.restic-env RELAY_DIR=/root/nostr-relay COPY=/var/backups/relay-db VOLUME=$(docker volume inspect --format '{{ .Mountpoint }}' nostr-relay_relay-db) mkdir -p "$COPY" cd "$RELAY_DIR" docker compose stop relay trap 'docker compose start relay' EXIT rsync -a --delete "$VOLUME/" "$COPY/" docker compose start relay trap - EXIT restic backup "$COPY" --tag relay restic forget --tag relay --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune -
Run it once by hand (
sudo /usr/local/bin/backup-relay), then schedule it withsudo crontab -e. The second line reads back a tenth of the stored data each month, a different tenth every time, to prove the copies can still be opened. Look at/var/log/backup-relay.lognow and then: a line withFatalmeans that night's copy failed.15 4 * * * /usr/local/bin/backup-relay >> /var/log/backup-relay.log 2>&1 45 5 1 * * . /root/.restic-env && restic check --read-data-subset=10% >> /var/log/backup-relay.log 2>&1
To restore (a new server, or after a disk failure): set the relay up again
(steps 1 to 3) and repeat points 1 and 2 of this step with the same repository,
but write the password you saved into /root/.restic-password instead of
creating a new one, and skip restic init. Then copy the latest backup into the
relay's volume:
sudo sh -c '. /root/.restic-env && restic restore latest --tag relay --target /'
cd /root/nostr-relay && sudo docker compose stop relay
sudo rsync -a --delete /var/backups/relay-db/ "$(sudo docker volume inspect --format '{{ .Mountpoint }}' nostr-relay_relay-db)/"
sudo docker compose start relay
Once a year, do this restore on a spare machine and check that the relay starts with your mail in it: it is the only test that proves the whole chain, password included.
Good to know
- It is a general-purpose relay. Besides your mail it accepts public notes from anyone, so other people could store things on your server. It has no setting to refuse them yet.
- Mail from ordinary email arrives through a bridge, which has to identify when your relay asks. If ordinary email stops arriving after the change, the public relay you kept in the list will still receive it.
- Back up the data (step 4). The envelopes live in the Docker volume
relay-db, and mail your app has not collected yet exists only there. - Your IP address. The relay sees the address of every app that connects to it, as any relay does. On your own server, the only one who sees them is you.