Outbound Service
The outbound service listens to Nostr events and sends them as legacy emails.
Architecture
┌──────┐ ┌────────┐ ┌─────────────────┐ ┌─────────────┐
│ User │ ───► │ Relays │ ───► │ Outbound Service│ ───► │ Legacy Email│
└──────┘ └────────┘ └─────────────────┘ └─────────────┘
Setup
cd bridge-outbound
npm install
cp .env.example .env
npm run build
npm start
Configuration
BRIDGE_PRIVATE_KEY=<hex>
RELAYS=wss://relay.damus.io,wss://nos.lol
# Outbound provider: 'smtp' or 'mailgun'
OUTBOUND_PROVIDER=smtp
FROM_DOMAIN=mail.example.com
# SMTP settings (when OUTBOUND_PROVIDER=smtp)
SMTP_HOST=localhost
SMTP_PORT=25
SMTP_SECURE=false
SMTP_USER=
SMTP_PASS=
# Mailgun settings (when OUTBOUND_PROVIDER=mailgun)
MAILGUN_API_KEY=key-xxx
MAILGUN_DOMAIN=mail.example.com
How It Works
-
Service subscribes to gift-wrapped events (kind 1059) for its pubkey
-
When an event arrives, it unwraps the NIP-59 encryption
-
Extracts the email content from the kind 1301 rumor
-
Reads the envelope from the rumor's tags:
mail-fromand onercpt-toper recipient -
Sends via configured provider (SMTP or Mailgun)
-
Reports the outcome with a kind 7679 event quoting the rumor's
email-id
The envelope is in the tags
Route on rcpt-to, not on the To: header. A Bcc recipient never appears in
the headers, and a header can say anything.
Processing Flow
flowchart TD
A[Subscribe to Kind 1059] --> B[Receive Gift-Wrapped Event]
B --> C[Unwrap NIP-59]
C --> D[Extract Kind 1301 Rumor]
D --> E[Parse Email Headers]
E --> F{To: is Legacy?}
F -->|No| G[Skip - Nostr Native]
F -->|Yes| H{Plugin Enabled?}
H -->|No| I[Send via Provider]
H -->|Yes| J[Run Plugin Filter]
J -->|Accept| I
J -->|Reject| K[Discard]
J -->|Shadow Reject| L[Silent Discard]
I --> M{SMTP or Mailgun?}
M -->|SMTP| N[Send via SMTP]
M -->|Mailgun| O[Send via Mailgun API]Email From Address
The bridge determines the From address:
- NIP-05 valid: If the email's
Fromis a valid NIP-05 that resolves toevent.pubkey, it's used as-is - Fallback: Otherwise, uses
npub@bridge-domain
# If alice@example.com resolves to event.pubkey
From: alice@example.com
# Otherwise, fallback to npub
From: npub1abc123...@yourdomain.com
SMTP Provider
For direct SMTP sending:
OUTBOUND_PROVIDER=smtp
SMTP_HOST=localhost # Your MTA (Postfix, etc.)
SMTP_PORT=25
SMTP_SECURE=false # true for TLS
SMTP_USER= # Optional auth
SMTP_PASS=
Mailgun Provider
For Mailgun API sending:
OUTBOUND_PROVIDER=mailgun
MAILGUN_API_KEY=key-xxx
MAILGUN_DOMAIN=mail.example.com
Event Format
The service expects events in this format:
{
"kind": 1059,
"pubkey": "<sender>",
"tags": [
["p", "<bridge_pubkey>"]
],
"content": "<encrypted_gift_wrap>"
}
After unwrapping, the inner rumor:
{
"kind": 1301,
"pubkey": "<sender_pubkey>",
"tags": [
["email-id", "550e8400-e29b-41d4-a716-446655440000"],
["mail-from", "npub1alice...@bridge.com"],
["rcpt-to", "recipient@gmail.com"]
],
"content": "From: npub1alice...@bridge.com\nTo: recipient@gmail.com\nSubject: Hello\n\nMessage body"
}
Reporting the outcome
Every send gets a kind 7679 delivery status notification, quoting the
email-id and encrypted to the sender with an ephemeral key. See
Delivery Status.