tpt-email

Go

Open-source reference gateway for Trust-First Email Protocol (TFEP) — cryptographic sender identity via Ed25519 + DID:web, spam/phishing prevention, verified webhooks, B2B document exchange, and non-repudiable legal notices. SMTP-compatible. Written in Go.

0 stars0 forks0 watchersMIT License
cryptographydecentralized-identitydid-webdnse2e-encryptioned25519emailemail-securitygatewaygolangidentitylegal-techopen-protocolphishing-preventionself-hostedsmtpspam-preventionwebhookszero-trust

Languages

Go67.3%JavaScript24.8%HTML7.7%Dockerfile0.1%CSS0.1%
README

TFEP Gateway

Trust-First Email Protocol — a backward-compatible email successor that cryptographically solves spam, phishing, and identity spoofing without breaking a single existing email client.

License: MIT Go Version Protocol


Three Problems. One Protocol.

TFEP's core primitive — cryptographic sender identity via Ed25519 + DID:web — solves the same trust gap in three high-value markets:

| Use Case | What you get | Guide | |---|---|---| | Verified Webhooks | Replace HMAC shared secrets with unforgeable DID-based identity. Any Go service can sign/verify in 3 lines. | WEBHOOKS.md | | B2B Document Exchange | Send invoices, contracts, and purchase orders with cryptographic sender proof and permit-based vendor authorization. No EDI VAN required. | B2B-EXCHANGE.md | | Legal / Regulatory Notices | Non-repudiable notice delivery with mandatory delivery receipts and timestamp proofs. | LEGAL-NOTICES.md |

5-minute quickstart


Why TFEP?

Email's trust problems are structural. SPF, DKIM, and DMARC are bolt-on fixes to a protocol that was never designed for verification. Anyone can forge "PayPal Security" <attacker@evil.com> and have it land in your inbox.

TFEP redesigns the trust layer from scratch while staying fully compatible with SMTP:

| Problem | How TFEP solves it | |---|---| | Spam | Senders declare message intent (personal, transactional, marketing). Marketing without a cryptographic opt-in permit is rejected at the protocol level — not filtered after delivery. | | Phishing | Every TFEP message carries an Ed25519 signature verifiable against the sender's published DID document. Forging did:web:paypal.com requires controlling PayPal's DNS and private key. | | Display-name spoofing | The verified DID domain is surfaced regardless of the From display name. Brand-name mismatches are flagged at the gateway. | | Unsolicited transactional mail | A fake "your account is suspended" email from an unknown sender has no relationship token and is downgraded to the held queue. |

TFEP does not require everyone to switch at once. It works as a dual-stack overlay: TFEP gateways speak both TFEP and SMTP, translating between them transparently.


Features

  • Cryptographic sender identity — Ed25519 signatures anchored to domain DNS via DID:web, no blockchain
  • End-to-end encryption — X25519 ECDH + NaCl secretbox, automatic when both parties have TFEP capability, graceful plaintext fallback to SMTP
  • Permission tokens — Recipients issue signed JWT permits; senders without one go to held queue
  • Message type enforcement — Marketing without opt-in permit rejected with SMTP 554; not filtered, rejected
  • Anti-phishing pipeline — Signature check, display-name mismatch detection, relationship token enforcement, replay prevention
  • Proof-of-work — Configurable Hashcash difficulty makes bulk sending expensive; exempt for permitted senders
  • Domain name registry — Lifetime registrations with cryptographic ownership transfer; no renewals
  • DNS bridge — Serves registered names under a custom TLD (.tpt) so addresses like alice@someone.tpt work
  • REST API + web UI — Inbox, held queue, permit management, contact book — no client install required
  • Prometheus metrics + structured logging — Production-ready observability
  • Single binarygo build produces one statically-linked executable; no runtime dependencies

Quick Start (5 minutes)

Install

go install github.com/tfep/tfep-gateway/cmd/tfep-gateway@latest

Or build from source:

git clone https://github.com/tfep/tfep-gateway.git
cd tfep-gateway
go build -o tfep-gateway ./cmd/tfep-gateway

Generate your identity

./tfep-gateway keygen --domain example.com --passphrase "your-secret"

This outputs:

  1. A DID document — save it at https://example.com/.well-known/did.json
  2. A DNS TXT record — add it to your DNS as _tfep.example.com
  3. Two encrypted key files: ed25519.pem, x25519.pem

Configure

Edit config.yaml:

domain: "example.com"

identity:
  did: "did:web:example.com"
  priv_key_file: "ed25519.pem"
  x25519_file: "x25519.pem"

api:
  key: "your-strong-api-key"

tls:
  cert_file: "/etc/letsencrypt/live/example.com/fullchain.pem"
  key_file:  "/etc/letsencrypt/live/example.com/privkey.pem"

Run

TFEP_KEY_PASSPHRASE="your-secret" ./tfep-gateway serve

Open http://localhost:8080 for the web inbox.


Architecture

                    ┌─────────────────────────────────────────┐
                    │             TFEP Gateway                 │
                    │                                          │
  SMTP sender ──────► :25 MTA server                          │
                    │     │                                    │
  TFEP sender ──────► :587 submission                         │
                    │     │                                    │
                    │     ▼                                    │
                    │  Trust Pipeline                          │
                    │  ① DNS TXT + DID:web lookup              │
                    │  ② Ed25519 signature verify              │
                    │  ③ Display-name spoof check              │
                    │  ④ Message type rule enforcement         │
                    │  ⑤ Permit / relationship token check     │
                    │  ⑥ Proof-of-work check                   │
                    │  ⑦ Replay / timestamp freshness check    │
                    │  ⑧ Route to trust-tiered queue           │
                    │                                          │
                    │  Tiers: verified / identified /          │
                    │         unknown / blocked                │
                    │                                          │
                    │  SQLite store ◄──── REST API :8080       │
                    │                         │                │
                    │                         ▼                │
                    │                     Web UI               │
                    └─────────────────────────────────────────┘

Trust Tiers

| Tier | Condition | Inbox routing | |------|-----------|---------------| | verified | Valid TFEP DNS record + DID document + Ed25519 signature | Inbox | | identified | DKIM/SPF/DMARC but no TFEP | Normal | | unknown | No verifiable authentication | Held queue | | blocked | Local blocklist match | Rejected |

Message Types

| Type | Authorization required | |------|----------------------| | personal | Permit or verified mutual contact | | transactional | Relationship token issued at signup | | marketing | Explicit opt-in permit from recipient | | automated | Same as transactional + rate limited |


CLI Reference

tfep-gateway [--config config.yaml] <command>

serve

Start all servers (MTA, submission, API).

tfep-gateway serve
TFEP_KEY_PASSPHRASE=secret tfep-gateway serve --config /etc/tfep/config.yaml

keygen

Generate a new Ed25519/X25519 keypair. Outputs DID document and DNS TXT record.

tfep-gateway keygen --domain example.com
tfep-gateway keygen --domain example.com --passphrase "secret"

rotate-key

Generate a replacement keypair for key rotation. Does not overwrite existing keys.

tfep-gateway rotate-key --domain example.com --passphrase "new-secret"
# Outputs: ed25519.new.pem, x25519.new.pem
# Update /.well-known/did.json, then replace the old key files and restart.

send

Send a signed (optionally encrypted) test message.

tfep-gateway send \
  --to alice@example.org \
  --type personal \
  --subject "Hello from TFEP" \
  --body "This message is cryptographically signed."

queue list

List stored messages with optional filters.

tfep-gateway queue list                    # all messages
tfep-gateway queue list --tier unknown     # held queue
tfep-gateway queue list --type marketing   # marketing messages
tfep-gateway queue list --thread <uuid>    # messages in a thread

queue release

Promote a held message to verified and auto-issue a send permit to the sender.

tfep-gateway queue release <message-id>

permits list / revoke

Manage active send permits.

tfep-gateway permits list
tfep-gateway permits revoke <permit-id>

store vacuum

Delete messages that have exceeded their retention policy.

tfep-gateway store vacuum

REST API

The API listens on port 8080 (configurable). All endpoints except /healthz, /metrics, and the well-known endpoints require Authorization: Bearer <api_key>.

Messages

| Method | Path | Description | |--------|------|-------------| | GET | /api/v1/messages | List messages. Query: ?tier=, ?type=, ?thread= | | GET | /api/v1/messages/{id} | Get a message | | GET | /api/v1/messages/{id}/raw | Get raw MIME (message/rfc822) | | POST | /api/v1/messages/{id}/release | Promote to verified + issue permit | | DELETE | /api/v1/messages/{id} | Delete |

Permits

| Method | Path | Description | |--------|------|-------------| | GET | /api/v1/permits | List active permits | | POST | /api/v1/permits | Issue permit {"grantee_did":"…","expiry_hours":8760} | | DELETE | /api/v1/permits/{id} | Revoke |

Contacts

| Method | Path | Description | |--------|------|-------------| | GET | /api/v1/contacts | List contacts | | POST | /api/v1/contacts | Add {"did":"…","display_name":"…"} | | DELETE | /api/v1/contacts/{id} | Remove |

Registry

| Method | Path | Description | |--------|------|-------------| | GET | /api/v1/registry/{name} | Resolve a registered name | | GET | /api/v1/registry/search?q= | Search names | | POST | /api/v1/registry/register | Register a name (auth required) | | POST | /api/v1/registry/transfer | Transfer ownership (auth required) |

System

| Method | Path | Description | |--------|------|-------------| | GET | /healthz | Health check + timestamp | | GET | /metrics | Prometheus metrics | | GET | /api/v1/stats | Queue depth by tier |

Well-known Protocol Endpoints

These are called by remote TFEP senders — no auth required.

| Method | Path | Description | |--------|------|-------------| | POST | /.well-known/tfep/permit-request | Sender requests a send permit | | POST | /.well-known/tfep/unsubscribe | Withdraw a marketing permit |


Configuration Reference

Full annotated config with all defaults:

# Your email domain. Must match the did:web domain.
domain: "example.com"

smtp:
  listen_addr: ":25"          # MTA inbound port (requires root or CAP_NET_BIND_SERVICE)
  submission_addr: ":587"     # Submission port (STARTTLS)
  max_msg_bytes: 26214400     # 25 MB

tls:
  cert_file: ""               # Path to TLS certificate (PEM)
  key_file: ""                # Path to TLS private key (PEM)

api:
  listen_addr: ":8080"        # REST API + web UI port
  key: ""                     # Bearer token (leave empty to disable auth in dev)

identity:
  did: "did:web:example.com"
  priv_key_file: "ed25519.pem"
  x25519_file: "x25519.pem"
  dkim_selector: ""           # DKIM selector for outbound signing (optional)
  dkim_key_file: ""           # DKIM private key file (optional)
  require_dnssec: false       # Hard-reject TFEP lookups without DNSSEC validation

trust:
  pow_difficulty: 0           # Hashcash bits required (0 = disabled, 16 = ~65k ops)
  permit_ttl: "8760h"         # Default permit validity (1 year)
  reputation_ttl: "720h"      # Reputation token validity (30 days)
  max_message_age: "5m"       # Reject messages older than this (replay prevention)
  clock_skew: "1m"            # Tolerate timestamps up to this far in the future
  send_receipts: false        # Send delivery acknowledgments to TFEP senders
  auto_approve_permits: false # Auto-grant permits from /.well-known/tfep/permit-request

  rate_limit:
    connections_per_minute: 20
    messages_per_hour: 100

  federated_reputation:
    enabled: false
    resolver: "rep.tfep.net"  # Community reputation DNS zone

store:
  path: "tfep.db"             # SQLite database file

  retention:                  # 0 = keep forever
    verified: "2160h"         # 90 days
    identified: "720h"        # 30 days
    unknown: "168h"           # 7 days
    blocked: "24h"            # 1 day

registry:
  enabled: false
  tld: "tpt"                  # Custom TLD for registered names (e.g. alice@someone.tpt)
  dormant_days: 90            # Days after registration before marking dormant

dns_bridge:
  enabled: false
  listen_addr: ":5353"        # DNS server port (use :53 for standard DNS)
  upstream: "1.1.1.1:53"     # Upstream resolver for non-registry queries
  gateway_ip: ""              # IP address returned for A records of registered names

log:
  level: "info"               # debug | info | warn | error
  format: "text"              # text | json

Environment Variables

All config values can be overridden with TFEP_ prefix environment variables:

TFEP_DOMAIN=example.com
TFEP_API_KEY=your-secret-key
TFEP_KEY_PASSPHRASE=your-key-passphrase
TFEP_LOG_LEVEL=debug

Deployment

Binary (recommended for VPS)

# Build
go build -ldflags="-s -w" -o tfep-gateway ./cmd/tfep-gateway

# Create system user
useradd -r -s /bin/false tfep

# Install
install -m 755 tfep-gateway /opt/tfep-gateway/
install -m 640 config.yaml /etc/tfep-gateway/

# Systemd
cp tfep-gateway.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now tfep-gateway

The systemd unit runs as a non-root user with NoNewPrivileges, PrivateTmp, and a capability bounding set limited to CAP_NET_BIND_SERVICE.

Docker

docker compose up -d

The docker-compose.yml mounts config.yaml and key files as read-only volumes and persists the SQLite database to a named volume.

# Minimal docker-compose.yml override for production
services:
  tfep-gateway:
    environment:
      - TFEP_DOMAIN=example.com
      - TFEP_API_KEY=your-strong-key
      - TFEP_KEY_PASSPHRASE=your-key-passphrase
    ports:
      - "25:25"
      - "587:587"
      - "8080:8080"

DNS Setup

After generating your keypair:

  1. TXT record — Add to DNS:

    _tfep.example.com TXT "v=TFEP1; did=did:web:example.com; policy=bridge; pow=0; caps=v1,enc,pow"
    
  2. DID document — Publish at https://example.com/.well-known/did.json (the keygen command outputs the exact JSON)

  3. MX record — Ensure your domain has an MX record pointing to this server's IP


Domain Name Registry

The optional registry allows permanent registration of short names under a custom TLD (default .tpt). Unlike traditional domain registrars, registrations are lifetime and transfers require a cryptographic signature from the current owner.

Enable in config:

registry:
  enabled: true
  tld: "tpt"

dns_bridge:
  enabled: true
  listen_addr: ":53"
  gateway_ip: "1.2.3.4"  # your server's public IP

Register a name:

curl -X POST http://localhost:8080/api/v1/registry/register \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"name":"alice","did":"did:web:example.com","owner_pubkey":"<hex-ed25519-pubkey>"}'

With the DNS bridge pointing at your gateway, alice.tpt will resolve to your gateway IP, allowing TFEP-native addresses like tfep@alice.tpt.


Protocol Specification

The full wire format, security properties, and migration strategy are documented in PROTOCOL.md.

Key sections:

  • §2 Identity model (DID:web, DNS TXT records, key rotation)
  • §3 Message format (MIME structure, envelope schema)
  • §4 Message types and authorization rules
  • §5 Trust tiers
  • §6 Permission tokens (send permits)
  • §7 End-to-end encryption
  • §8 Proof-of-work
  • §9 Anti-phishing enforcement
  • §10 Reputation tokens (SMTP bootstrap)
  • §11 Federated reputation
  • §15 Migration path

Roadmap

The v0.1.0 release is a complete reference implementation. Planned community work:

  • IMAP4 server — so existing email clients (Thunderbird, Apple Mail) can access TFEP inboxes
  • Native client — web and mobile clients that show trust badges, thread views, and verified sender information
  • ACME/Let's Encrypt — automatic TLS certificate provisioning
  • Community reputation zonerep.tfep.net DNS infrastructure for federated spam scoring
  • Formal security audit — third-party review of the cryptographic layer

See CONTRIBUTING.md for areas that need help.


Contributing

Contributions are welcome. Read CONTRIBUTING.md for the development setup, code standards, and process.

To report a security vulnerability, see SECURITY.md.


License

MIT — see LICENSE.

Built with: go-smtp, go-message, go-msgauth, miekg/dns, modernc/sqlite, golang-jwt/jwt, prometheus/client_golang, spf13/cobra, spf13/viper.