Cooperative email for PDS operators
Go 84%
Assembly 8%
Astro 2%
2%
Nix 1%
Shell <1%
templ <1%
TypeScript <1%
CSS <1%
Standard ML <1%
HTML <1%
HCL <1%
Makefile <1%
JavaScript <1%
Python <1%
C <1%
Ruby <1%
Dockerfile <1%

README.md

comail #

A cooperative email reputation layer for atproto. PDS operators pool sending volume and reputation through shared infrastructure, verified by a labeling service that checks DNS configuration and publishes signed attestations to the atproto network.

Starting with transactional email (verification codes, password resets, notifications) to build collective domain reputation, with the long-term goal of making self-hosted personal email viable again.

See comail-vision.md for the full proposal.

Status #

Alpha. The relay, labeler, web frontend, and admin dashboard are live; the cooperative is accepting its first external members.

  • Relay runs at smtp.atmos.email with STARTTLS on 587 and inbound FBL/DSN on 25.
  • Labeler consumes the atproto firehose via Jetstream and issues verified-mail-operator + relay-member labels.
  • Every outbound message is dual-signed (d=<member-domain> for DMARC alignment, d=atmos.email for pool-level FBL) and carries Feedback-ID + X-Atmos-Member-Did for attribution.
  • Warming tier caps protect the shared IP during the first 14 days of a new member's lifetime (2/hour, 10/day weeks 1; 10/hour, 50/day week 2).
  • Per-domain auto-pause on 5% bounce rate or 0.08% spam-complaint rate over a 24h window.
  • Pool-level FBL registrations: Gmail Postmaster verified, Microsoft SNDS + JMRP registered, Yahoo CFL verified.

To enroll a new sending domain, visit https://comail.at and sign in with your atproto handle. The flow handles atproto OAuth, DKIM keypair issuance, and the DNS records you need to publish.

How it works #

  1. A PDS operator publishes an email.atmos.attestation record declaring their mail domain and DKIM selectors
  2. The labeler watches the atproto firehose (via Jetstream) for these records
  3. For each attestation, it verifies:
    • Domain control: the operator's DID handle matches the domain, or a _atproto.<domain> TXT record points to the DID
    • MX: at least one MX record exists
    • SPF: a v=spf1 record exists without +all
    • DKIM: every declared selector has a v=DKIM1 record
    • DMARC: a record exists at _dmarc.<domain> with policy quarantine or reject
  4. If all checks pass, the labeler signs and publishes verified-mail-operator (and optionally relay-member) labels on the operator's DID
  5. Labels are queryable via standard atproto XRPC endpoints (com.atproto.label.queryLabels, com.atproto.label.subscribeLabels)
  6. A scheduler re-verifies and negates labels if DNS degrades. Default interval is 24h; production runs at 1h via NixOS config.

Lexicons #

The NSID namespace is email.atmos.*, backed by atmos.email. See lexicons/README.md for schemas and open questions.

Repository layout #

cmd/
  relay/        Mail engine: SMTP submission, /v1/send HTTP API, queue, DKIM, inbound DSN/FBL
  web/          Public HTTP frontend: marketing, /signup, /account, /docs, OAuth callback
  labeler/      atproto attestation verifier + label issuer (CBOR-signed labels)
  label-api/    PostgreSQL bridge for Osprey to query labeler state
  rotate-dkim/  Helper to rotate per-domain DKIM keys
  sendtest/     Manual SMTP test harness
internal/
  relay/        Queue, warming, DKIM signer, category policy, suppression, bounces
  relaystore/   SQLite schema + migrations for the relay
  admin/        Admin HTTP API + member self-service endpoints
  atpoauth/     atproto OAuth client (DPoP, PAR, callback handling)
  sessions/     JWT issuer/verifier for enroll-auth + recovery flows
  jetstream/    Firehose consumer with collection filtering
  scheduler/    Re-verification scheduler + PLC tombstone polling
  osprey/       Trust-and-safety event emitter (Kafka, on-disk DLQ)
  dns/          MX, SPF, DKIM, DMARC verifier (injectable resolver)
  domain/       DID-to-domain control verification
  ...
web-frontend/   Astro SPA (per-domain dashboard, signup, marketing pages)
lexicons/       email.atmos.* lexicon definitions
infra/          Terraform (DNS via Bunny) + NixOS modules for relay + ops hosts
docs/           Operator runbook, deployment specs, audits

Building #

The repo is a monorepo with multiple binaries. Build everything:

go build ./...

Or one at a time:

go build ./cmd/relay     # mail engine
go build ./cmd/web       # HTTP frontend
go build ./cmd/labeler   # attestation verifier + label issuer
go build ./cmd/label-api # PostgreSQL label bridge

Running the labeler #

The labeler is the easiest component to run in isolation (no SMTP, no Postgres dependency). Useful as a starting point.

Initialize state directory and signing key:

./labeler -init

This creates ./state/config.json (if missing) and generates a secp256k1 signing key. The labeler's did:key is printed to stdout.

Start the labeler:

./labeler -config ./state/config.json

Labeler configuration #

Copy config.json.example to ./state/config.json. All fields have defaults:

Field Default Description
listenAddr :8081 XRPC server bind address
stateDir ./state SQLite database (labels.sqlite) and key storage
jetstreamURL wss://jetstream1.us-east.bsky.network/subscribe Jetstream endpoint
signingKeyPath ./state/signing.key secp256k1 private key (hex)
reverifyInterval 24h Re-verification cadence (production deploys override to 1h)

Config files support comments and trailing commas (hujson).

Running the relay + web #

The relay and web binaries are deployed together on a VPS via NixOS. See infra/nixos/ for the production modules and docs/operator-runbook.md for the operational playbook. A standalone dev setup is non-trivial (requires Postgres for label-api, optional Kafka for Osprey, and DNS for the labeler to verify against); the runbook is the canonical guide.

Testing #

go test ./...

Architecture #

See docs/operator-runbook.md for the live deployment topology and docs/phase-5c-spec.md for the current HTTP-split design (cmd/relay owns the public TLS listener and reverse-proxies HTML routes to cmd/web on loopback).

License #

AGPL-3.0-or-later. See LICENSE.

Community #

  • CONTRIBUTING: workflow, branching, commits, DCO sign-off.
  • CODE_OF_CONDUCT: Contributor Covenant 2.1.
  • SECURITY: vulnerability disclosure policy. Report to security@comail.at.

Author #

Scott Lanoue (@scottlanoue.com)