Herkos

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

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

  1. In Herkos, open Settings → Network and servers → Your own relay and tap Check a relay. Enter wss://relay.example.com.
  2. 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.
  3. 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.

  1. 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.pub there).

    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'
    
  2. Save this as /usr/local/bin/backup-relay and make it executable (sudo chmod 700 /usr/local/bin/backup-relay). Change RELAY_DIR if 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
    
  3. Run it once by hand (sudo /usr/local/bin/backup-relay), then schedule it with sudo 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.log now and then: a line with Fatal means 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