BRIDGE_DEPLOYMENT.md raw

Nostr-Email Bridge Deployment Guide

This document covers deploying the ORLY Nostr-Email bridge (Marmot DM to SMTP).

Architecture Overview

The bridge converts between Nostr Marmot DMs and standard email:

Deployment Modes

ModeDescriptionConfig
MonolithicBridge runs inside the ORLY relay processORLY_BRIDGE_ENABLED=true
StandaloneBridge runs as a separate processorly bridge subcommand
LauncherBridge managed by the process supervisorORLY_LAUNCHER_BRIDGE_ENABLED=true

Monolithic is simplest for single-server deployments. Standalone is useful when the relay and bridge run on different hosts.

Quick Start (Monolithic)

# 1. Generate DKIM keys
./scripts/generate-dkim.sh yourdomain.com

# 2. Set environment variables
export ORLY_BRIDGE_ENABLED=true
export ORLY_BRIDGE_DOMAIN=yourdomain.com
export ORLY_BRIDGE_SMTP_PORT=2525
export ORLY_BRIDGE_DKIM_KEY=/path/to/dkim-private.pem
export ORLY_BRIDGE_DKIM_SELECTOR=marmot
export ORLY_BRIDGE_COMPOSE_URL=https://yourdomain.com/compose

# 3. Start relay (bridge starts automatically)
./orly

The compose form is served at /compose and the decrypt page at /decrypt.


DNS Configuration

All DNS records are required for email deliverability. Without them, outbound email will be rejected by most providers.

MX Record

Points incoming email for your domain to your bridge server.

yourdomain.com.  IN  MX  10  mail.yourdomain.com.
mail.yourdomain.com.  IN  A  <YOUR_SERVER_IP>

If your relay is the same host:

yourdomain.com.  IN  MX  10  yourdomain.com.

SPF Record

Authorizes your server to send email for the domain.

yourdomain.com.  IN  TXT  "v=spf1 ip4:<YOUR_SERVER_IP> -all"

For multiple IPs:

yourdomain.com.  IN  TXT  "v=spf1 ip4:1.2.3.4 ip4:5.6.7.8 -all"

DKIM Record

The generate-dkim.sh script outputs the exact DNS record. The selector defaults to marmot.

marmot._domainkey.yourdomain.com.  IN  TXT  "v=DKIM1; k=rsa; p=MIIBIjANBg..."

The value is the base64-encoded RSA public key (no line breaks in the DNS TXT record).

DMARC Record

Tells receivers how to handle mail that fails SPF/DKIM.

_dmarc.yourdomain.com.  IN  TXT  "v=DMARC1; p=quarantine; rua=mailto:postmaster@yourdomain.com"

For testing, start with p=none (report only, don't reject):

_dmarc.yourdomain.com.  IN  TXT  "v=DMARC1; p=none; rua=mailto:postmaster@yourdomain.com"

Reverse DNS (PTR)

The PTR record maps your server's IP address back to your domain. Mail servers check that the IP sending mail resolves to the domain it claims to be.

<YOUR_IP>  IN  PTR  yourdomain.com.

Important ordering: The forward A record (yourdomain.com → <IP>) must exist and propagate before you set the PTR. Many providers verify the forward record when you create the reverse, and will reject or silently remove the PTR if the A record doesn't match.

How to set it: PTR records are set by the IP owner (your VPS provider), not the domain registrar. Look for "Reverse DNS", "rDNS", or "PTR" in your provider's control panel, usually under Networking or IP settings.

ProviderWhere to find it
HetznerCloud Console → Server → Networking → Primary IPs → Edit
DigitalOceanDroplet → Networking → PTR Records
VultrServer → Settings → IPv4 → Reverse DNS
Linode/AkamaiLinode → Network → IP Addresses → Edit rDNS
MynymboxManagement panel → Reverse DNS

Verify after setting:

dig -x YOUR_SERVER_IP +short
# Expected: yourdomain.com.

If the output still shows the provider's default hostname (e.g., static.50.235.71.80.virtservers.com), the PTR hasn't propagated or wasn't accepted. Check that your A record is correct and retry.


DKIM Key Generation

Use the provided script:

./scripts/generate-dkim.sh yourdomain.com [selector]

This generates:

If you prefer manual generation:

openssl genrsa -out dkim-private.pem 2048
openssl rsa -in dkim-private.pem -pubout -outform DER | openssl base64 -A
# Use the base64 output as the "p=" value in the DNS TXT record

Port 25 Workarounds

Many cloud providers block port 25 by default. The bridge listens on port 2525 (configurable via ORLY_BRIDGE_SMTP_PORT). You need to get traffic from port 25 to your bridge port.

Method 1: iptables Redirect (Recommended)

Redirect port 25 to your bridge port. No binary changes needed.

sudo iptables -t nat -A PREROUTING -p tcp --dport 25 -j REDIRECT --to-port 2525

Make it persistent across reboots:

sudo apt install iptables-persistent
sudo netfilter-persistent save

Method 2: setcap (Bind Port 25 Directly)

Grant the binary permission to bind low ports without root:

sudo setcap 'cap_net_bind_service=+ep' ./orly

Then set ORLY_BRIDGE_SMTP_PORT=25.

Note: setcap is cleared if you replace the binary, so re-run after each deploy.

Method 3: systemd Socket Activation

systemd listens on port 25 and passes the file descriptor to the bridge.

# /etc/systemd/system/orly-smtp.socket
[Socket]
ListenStream=25
Accept=no

[Install]
WantedBy=sockets.target

Then configure the bridge to accept the systemd socket. This requires custom code not yet in the bridge — use iptables for now.

Method 4: Provider Unblock Request

Some providers will open port 25 on request:

ProviderProcess
DigitalOceanSupport ticket, explain mail server use case
VultrSupport ticket
Linode/AkamaiSupport ticket (usually approved for new accounts after a period)
AWS EC2Request via SES, or use an Elastic IP with port 25 unblock form

Method 5: Providers with Port 25 Open

These providers do not block port 25 by default:

Method 6: SSH Tunnel

If you have a separate server with port 25 open:

# On the port-25-capable server:
ssh -R 2525:localhost:2525 user@bridge-host

Or use autossh for persistent tunnels:

autossh -M 0 -N -R 25:localhost:2525 user@bridge-host

Method 7: WireGuard / VPN Tunnel

Run a VPN between a port-25-capable server and your bridge host:

# On the port-25 server, forward port 25 to the bridge via WireGuard peer IP
iptables -t nat -A PREROUTING -p tcp --dport 25 -j DNAT --to-destination 10.0.0.2:2525
iptables -t nat -A POSTROUTING -j MASQUERADE

Method 8: Docker with Host Networking

docker run --network host -e ORLY_BRIDGE_SMTP_PORT=2525 ...
# Then use iptables method above on the host

Method 9: Separate Mail Server (Postfix Relay)

Run Postfix on a port-25-capable host and pipe to the bridge via LMTP or HTTP webhook:

# /etc/postfix/main.cf
transport_maps = hash:/etc/postfix/transport

# /etc/postfix/transport
yourdomain.com  lmtp:bridge-host:2525

This is the most complex option but works well if you already run a mail server.


Environment Variables

All bridge configuration is via environment variables with the ORLY_BRIDGE_ prefix.

VariableDefaultDescription
ORLY_BRIDGE_ENABLEDfalseEnable the email bridge
ORLY_BRIDGE_DOMAINEmail domain (e.g., relay.example.com)
ORLY_BRIDGE_NSECBridge identity nsec (default: relay identity from database)
ORLY_BRIDGE_RELAY_URLThe relay that the bridge will connect to - use the public URL even if running in monolithic (single binary, no gRPC bridging) mode. Also allows remote relays to be used in the same way as the bridge is a standalone unit.
ORLY_BRIDGE_SMTP_PORT2525SMTP server listen port
ORLY_BRIDGE_SMTP_HOST0.0.0.0SMTP server listen address
ORLY_BRIDGE_DATA_DIR$ORLY_DATA_DIR/bridgeBridge data directory
ORLY_BRIDGE_DKIM_KEYPath to DKIM private key PEM file
ORLY_BRIDGE_DKIM_SELECTORmarmotDKIM selector for DNS TXT record
ORLY_BRIDGE_NWC_URINWC connection string for subscription payments (falls back to ORLY_NWC_URI)
ORLY_BRIDGE_MONTHLY_PRICE_SATS2100Monthly subscription price in sats
ORLY_BRIDGE_ALIAS_PRICE_SATS4200Monthly alias email price in sats
ORLY_BRIDGE_COMPOSE_URLPublic URL of the compose form
ORLY_BRIDGE_SMTP_RELAY_HOSTSMTP smarthost for outbound delivery (e.g., smtp.migadu.com)
ORLY_BRIDGE_SMTP_RELAY_PORT587SMTP smarthost port (587 for STARTTLS)
ORLY_BRIDGE_SMTP_RELAY_USERNAMESMTP smarthost AUTH username
ORLY_BRIDGE_SMTP_RELAY_PASSWORDSMTP smarthost AUTH password
ORLY_BRIDGE_ACL_GRPC_SERVERgRPC address of ACL server (split IPC mode with paid ACL)
ORLY_BRIDGE_PROFILE$DATA_DIR/profile.txtPath to profile template file (kind 0 metadata)

NWC Wallet Setup

The bridge uses Nostr Wallet Connect (NWC) to create Lightning invoices for subscription payments. You need a Lightning wallet that supports NWC and can generate a connection string.

Getting an NWC Connection String

Option A: Alby (hosted, simplest)

  1. Go to getalby.com and create an account (or log in)
  2. Go to Settings → Wallet Connections (or visit getalby.com/apps)
  3. Click "Create a new connection"
  4. Name it (e.g., "Marmot Bridge")
  5. Under permissions, enable only: make_invoice and lookup_invoice
  6. Do not enable pay_invoice or any spending permissions
  7. Copy the connection string — it looks like: nostr+walletconnect://pubkey?relay=wss://...&secret=...

Option B: Self-hosted (Alby Hub, LNbits, NWC-enabled node)

Most self-hosted Lightning wallets with NWC support have an "Apps" or "Connections" section where you can create restricted connection strings. The process is similar — create a new app connection with only make_invoice and lookup_invoice permissions.

Set the Environment Variable

export ORLY_BRIDGE_NWC_URI="nostr+walletconnect://pubkey?relay=wss://relay.getalby.com/v1&secret=hexsecret"

Or use ORLY_NWC_URI if the bridge shares the wallet with the relay.

The bridge only needs to create invoices and check payment status. It never needs pay_invoice or any spending capability. If your wallet forces you to grant all permissions, create a separate wallet for the bridge.


Subscription Model

Users send the word subscribe as a DM to the bridge identity. The bridge:

  1. Creates a Lightning invoice via NWC
  2. Sends the invoice back as a DM
  3. Polls for payment (configurable interval)
  4. On payment: activates a 30-day subscription, confirms via DM

Subscription state is stored in $ORLY_BRIDGE_DATA_DIR/subscriptions.json.

Rate limits for subscribed users:

Bridge Profile (Kind 0)

The bridge can publish a kind 0 (profile metadata) event on startup so Nostr clients can discover it and display a name, avatar, and description. Without a profile, clients show the bridge as an unknown pubkey.

Create a profile.txt in the bridge data directory using email-header format:

name: Marmot Bridge
about: Nostr-Email bridge at yourdomain.com. DM 'subscribe' to get started.
picture: https://yourdomain.com/avatar.png
nip05: bridge@yourdomain.com
lud16: tips@yourdomain.com
website: https://yourdomain.com
banner: https://yourdomain.com/banner.png

The bridge reads this file on every startup and publishes a kind 0 event signed with its identity. Lines with empty values are omitted. Comments (lines starting with #) are ignored.

To use a custom path instead of $ORLY_BRIDGE_DATA_DIR/profile.txt:

export ORLY_BRIDGE_PROFILE=/etc/orly/bridge-profile.txt

A template is provided at profile.example.txt in the repository root.


Blossom Server Configuration

The bridge uses Blossom for attachment storage. Ensure Blossom is enabled:

ORLY_BLOSSOM_ENABLED=true

When an inbound email has non-plaintext attachments, the bridge:

  1. Bundles them into a flat ZIP
  2. Encrypts with ChaCha20-Poly1305 (random key)
  3. Uploads to Blossom as an opaque blob
  4. Includes the URL with #key fragment in the DM

The encryption key is in the URL fragment, which per RFC 3986 is never sent to the server. Only the DM recipient (who has the full URL) can decrypt.


Compose Form

The compose form at /compose is a static HTML page with no server-side logic. It:

The decrypt page at /decrypt allows email recipients to decrypt attachment URLs:

Pre-Flight Checklist

Run this before requesting port 25 from your provider, or before going live. Every check must pass.

# Replace with your actual domain and IP
DOMAIN=yourdomain.com
IP=1.2.3.4

echo "=== MX ==="
dig MX $DOMAIN +short
# Expected: 10 yourdomain.com.  (or your MX host)

echo "=== SPF ==="
dig TXT $DOMAIN +short | grep spf
# Expected: "v=spf1 ip4:$IP -all"

echo "=== DKIM ==="
dig TXT marmot._domainkey.$DOMAIN +short
# Expected: "v=DKIM1; k=rsa; p=MIIBIj..."

echo "=== DMARC ==="
dig TXT _dmarc.$DOMAIN +short
# Expected: "v=DMARC1; p=none; ..." or "p=quarantine"

echo "=== PTR ==="
dig -x $IP +short
# Expected: yourdomain.com.  (MUST match your domain, not the provider default)

echo "=== A record (forward) ==="
dig A $DOMAIN +short
# Expected: $IP

If PTR still shows the provider's default hostname, see the Reverse DNS section above. Many providers require the forward A record to exist before they accept the PTR.

Also check MX Toolbox email health for your domain — it flags common issues that will cause mail rejection.


Troubleshooting

Verify DNS Records

# Check MX record
dig MX yourdomain.com +short

# Check SPF
dig TXT yourdomain.com +short

# Check DKIM
dig TXT marmot._domainkey.yourdomain.com +short

# Check DMARC
dig TXT _dmarc.yourdomain.com +short

# Check PTR (reverse DNS)
dig -x YOUR_SERVER_IP +short

Online Tools

Common Issues

Outbound email rejected as spam:

SMTP connection refused on port 25:

Bridge identity mismatch:

Subscription payments not working:

Attachments not working:

Example: Full Production Setup

# DNS records (set via your registrar):
# yourdomain.com  MX 10  yourdomain.com
# yourdomain.com  TXT    "v=spf1 ip4:1.2.3.4 -all"
# marmot._domainkey.yourdomain.com  TXT  "v=DKIM1; k=rsa; p=..."
# _dmarc.yourdomain.com  TXT  "v=DMARC1; p=quarantine; rua=mailto:postmaster@yourdomain.com"

# Generate DKIM key
./scripts/generate-dkim.sh yourdomain.com marmot

# Environment
export ORLY_PORT=3334
export ORLY_BRIDGE_ENABLED=true
export ORLY_BRIDGE_DOMAIN=yourdomain.com
export ORLY_BRIDGE_SMTP_PORT=2525
export ORLY_BRIDGE_DKIM_KEY=/etc/orly/dkim-private.pem
export ORLY_BRIDGE_DKIM_SELECTOR=marmot
export ORLY_BRIDGE_COMPOSE_URL=https://yourdomain.com/compose
export ORLY_BRIDGE_NWC_URI="nostr+walletconnect://..."
export ORLY_BRIDGE_MONTHLY_PRICE_SATS=2100
export ORLY_BLOSSOM_ENABLED=true

# Redirect port 25 to bridge SMTP port
sudo iptables -t nat -A PREROUTING -p tcp --dport 25 -j REDIRECT --to-port 2525
sudo netfilter-persistent save

# Start
./orly

SMTP Smarthost (Migadu, Mailgun, etc.)

Instead of direct MX delivery with self-managed DKIM/SPF, you can relay outbound email through a hosted email provider. This simplifies DNS setup and improves deliverability since the provider handles IP reputation.

When ORLY_BRIDGE_SMTP_RELAY_HOST is set, the bridge sends all outbound mail through the smarthost using STARTTLS + PLAIN authentication instead of direct MX delivery.

export ORLY_BRIDGE_SMTP_RELAY_HOST=smtp.migadu.com
export ORLY_BRIDGE_SMTP_RELAY_PORT=587
export ORLY_BRIDGE_SMTP_RELAY_USERNAME=bridge@yourdomain.com
export ORLY_BRIDGE_SMTP_RELAY_PASSWORD=your-app-password

The bridge's own DKIM signing (ORLY_BRIDGE_DKIM_KEY) is bypassed when using a smarthost since the provider signs outbound mail with their own DKIM keys.


Example: Migadu Deployment (relay.orly.dev)

This is a worked example of deploying the bridge on a Linode VPS using Migadu as the hosted email provider for the subdomain relay.orly.dev. The domain registrar is Namecheap.

1. Add Domain in Migadu

Log into admin.migadu.com, go to Domains, add relay.orly.dev.

Migadu will show a list of required DNS records and a verification TXT value unique to your account.

2. DNS Records (Namecheap)

In Namecheap's Advanced DNS panel for orly.dev, add the following records. Since relay.orly.dev is a subdomain, the Host field is relay (not @).

MX Records

TypeHostValuePriorityTTL
MXrelayaspmx1.migadu.com101 min
MXrelayaspmx2.migadu.com201 min

TXT Records

TypeHostValueTTL
TXTrelayv=spf1 include:spf.migadu.com -all1 min
TXTrelayhosted-email-verify=XXXXXXXX1 min
TXT_dmarc.relayv=DMARC1; p=quarantine;1 min

Replace XXXXXXXX with the verification value from Migadu's admin panel.

CNAME Records (DKIM)

TypeHostValueTTL
CNAMEkey1._domainkey.relaykey1.relay.orly.dev._domainkey.migadu.com1 min
CNAMEkey2._domainkey.relaykey2.relay.orly.dev._domainkey.migadu.com1 min
CNAMEkey3._domainkey.relaykey3.relay.orly.dev._domainkey.migadu.com1 min

The A record for relay.orly.dev pointing to the VPS (e.g., 69.164.249.71) remains unchanged. MX and A records coexist without conflict.

3. Verify in Migadu

After adding DNS records (propagation is typically under 5 minutes with 1-minute TTL), go back to Migadu's domain page and run the DNS check. All checks should pass.

Verify locally:

dig relay.orly.dev MX +short
# Expected: 10 aspmx1.migadu.com.  20 aspmx2.migadu.com.

dig relay.orly.dev TXT +short
# Expected: "v=spf1 include:spf.migadu.com -all"  "hosted-email-verify=..."

dig _dmarc.relay.orly.dev TXT +short
# Expected: "v=DMARC1; p=quarantine;"

dig key1._domainkey.relay.orly.dev CNAME +short
# Expected: key1.relay.orly.dev._domainkey.migadu.com.

4. Create Mailbox in Migadu

In Migadu admin, create a mailbox for the bridge (e.g., bridge@relay.orly.dev). Note the password — this is used for SMTP authentication.

5. Inbound Email Forwarding

Migadu receives inbound email for relay.orly.dev via its MX servers. To deliver it to the bridge's SMTP server on the VPS, set up forwarding:

Option A: Migadu forwards to a subdomain with direct MX

Create a subdomain bridge.relay.orly.dev with MX pointing directly at the VPS:

TypeHostValuePriority
MXbridge.relayrelay.orly.dev10

In Migadu, set up a catch-all forwarding rule to forward *@relay.orly.dev to incoming@bridge.relay.orly.dev.

On the VPS, open port 25 and redirect to the bridge's SMTP port:

sudo iptables -t nat -A PREROUTING -p tcp --dport 25 -j REDIRECT --to-port 2525
sudo netfilter-persistent save

Option B: Migadu sieve forwarding to VPS

In Migadu's mailbox settings, add a sieve filter or forwarding rule that re-sends incoming mail to an address handled by the bridge's SMTP server.

6. Bridge Environment

Add to the ORLY service environment (.env file or systemd unit):

ORLY_BRIDGE_ENABLED=true
ORLY_BRIDGE_DOMAIN=relay.orly.dev
ORLY_BRIDGE_SMTP_PORT=2525
ORLY_BRIDGE_SMTP_HOST=0.0.0.0
ORLY_BRIDGE_SMTP_RELAY_HOST=smtp.migadu.com
ORLY_BRIDGE_SMTP_RELAY_PORT=587
ORLY_BRIDGE_SMTP_RELAY_USERNAME=bridge@relay.orly.dev
ORLY_BRIDGE_SMTP_RELAY_PASSWORD=<migadu-password>
ORLY_BRIDGE_NWC_URI=nostr+walletconnect://...
ORLY_BRIDGE_MONTHLY_PRICE_SATS=2100
ORLY_BRIDGE_COMPOSE_URL=https://relay.orly.dev/compose

7. Build and Deploy

# Build unified binary with web UI
./scripts/update-embedded-web.sh
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o orly ./cmd/orly

# Deploy
ssh root@relay.orly.dev 'systemctl stop orly'
rsync -avz --compress -e "ssh -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes" \
  orly root@relay.orly.dev:/home/mleku/.local/bin/
ssh root@relay.orly.dev 'chown mleku:mleku /home/mleku/.local/bin/orly && systemctl start orly'

8. Verify

# Check bridge started
ssh root@relay.orly.dev 'journalctl -u orly --since "1 min ago" | grep bridge'
# Expected: "bridge identity: npub1..." and "bridge started"

# Test outbound: DM the bridge npub with "subscribe"
# Test inbound: send email to npub1...@relay.orly.dev

Client Setup: White Noise

White Noise is a mobile/desktop Nostr messaging client that uses NIP-17 gift-wrapped DMs. When searching for the bridge by its npub, White Noise may show the profile but say the bridge "isn't on White Noise yet." This means White Noise cannot find the bridge's relay list and doesn't know where to send messages.

Why This Happens

White Noise (and other NIP-17 clients) discover where to send DMs by looking up the recipient's kind 10002 event (NIP-65 relay list metadata). This event tells clients which relays a user reads from and writes to. Without it, the client has no delivery target for gift-wrapped DMs.

The bridge currently publishes a kind 0 (profile) on startup but does not publish a kind 10002 relay list. Until the bridge publishes one automatically, you need to publish it manually.

Publishing a Relay List for the Bridge

Use any Nostr tool that can publish events with an arbitrary key. The event must be signed by the bridge's identity key (the nsec used for ORLY_BRIDGE_NSEC, or the relay's auto-generated identity if no explicit nsec is set).

The kind 10002 event looks like:

{
  "kind": 10002,
  "content": "",
  "tags": [
    ["r", "wss://relay.orly.dev/"],
    ["r", "wss://relay.damus.io/", "read"],
    ["r", "wss://nos.lol/", "read"]
  ]
}

Each r tag is a relay URL. Tags without a marker (no third element) mean both read and write. "read" means the bridge reads from that relay but doesn't write there. "write" means write-only.

At minimum, include the bridge's own relay as a read+write relay:

["r", "wss://your-relay-domain/"]

Adding popular public relays as read entries helps clients that don't connect to your relay directly find the bridge.

Step-by-Step with nak

nak is a command-line Nostr tool. Install it, then publish the relay list:

# Set the bridge's secret key
export NOSTR_SECRET_KEY=nsec1...  # The bridge identity nsec

# Publish kind 10002 with your relay as read+write, plus popular relays as read-only
nak event \
  --kind 10002 \
  --tag r='wss://relay.orly.dev/' \
  --tag r='wss://relay.damus.io/;read' \
  --tag r='wss://nos.lol/;read' \
  wss://relay.orly.dev wss://relay.damus.io wss://nos.lol

Publish to multiple relays so clients can discover it regardless of which relays they check.

Step-by-Step with nostril

nostril is another CLI option:

nostril --kind 10002 \
  --sec <bridge-hex-secret> \
  --tag r 'wss://relay.orly.dev/' \
  --tag r 'wss://relay.damus.io/' read \
  --tag r 'wss://nos.lol/' read \
  | websocat wss://relay.orly.dev

Verifying the Relay List

After publishing, verify that clients can find it:

# With nak
nak req -k 10002 -a <bridge-pubkey-hex> wss://relay.orly.dev

# Or check on a web viewer
# https://njump.me/<bridge-npub>

White Noise should now show the bridge as reachable. Search for the bridge npub again and the "isn't on White Noise yet" message should be replaced with the ability to start a conversation.

Recommended Relay List

For maximum discoverability, include the bridge's own relay plus 2-3 popular relays:

RelayMarkerPurpose
wss://your-relay.com/(none)Primary — bridge reads and writes here
wss://relay.damus.io/readPopular relay, helps discovery
wss://nos.lol/readPopular relay, helps discovery
wss://relay.nostr.band/readIndexer relay, helps discovery

Future: Automatic Relay List Publishing

A future ORLY release will have the bridge publish kind 10002 automatically on startup alongside the kind 0 profile, eliminating this manual step.