Security
relay is communication infrastructure. Your application trusts relay with
message content and DNS authority for your domains. This page explains the
mechanisms that make that trust safe. Every claim here describes behavior
that relay implements today.
Account security
relay authenticates you through GitHub OAuth. relay stores your GitHub
identity and your email address, but never a password. GitHub proves who you
are on every sign-in. A password leak from the relay database is impossible
by design, because relay holds no passwords.
Sign-in creates your personal organization on the first use. An organization
owns domains, SMTP credentials, webhooks, and messages. relay isolates
organization data at the database level. Every query of org-owned data filters on the
organization, so one organization cannot read the messages of another
organization.
The dashboard answers over HTTPS only, and its HSTS policy keeps browsers from
falling back to plain HTTP.
relay also blocks domain hijacking between organizations: a domain that
overlaps a domain of another organization cannot be registered. relay reserves subdomains
of the managed sender domain for the platform. relay enforces
both rules before it saves the domain.
SMTP credential security
Your application submits email over the relay submission host with an
SMTP credential. A credential belongs to exactly one organization. Each
credential carries a visible key prefix, so you can tell credentials apart in
the dashboard and in support conversations.
relay does not store API keys in plain form. relay stores a key prefix and a
key hash, and only the hash can answer an authentication attempt. The plain
API-key value is visible once at creation and never again. Authentication
looks up credentials by organization and prefix and verifies the key hash.
A sandbox credential exists for testing, and relay never transmits its
messages to the recipient’s mail server.
Any credential can carry a hold flag. A held credential fails
authentication immediately without deletion. This buys you a pause button for
suspicious activity.
Create one credential per application, and give each one a name. The stored
message records the credential that submitted it, so every send is
attributable. If a key leaks, delete it in the dashboard, then create a
replacement. Rotating credentials changes no message content.
Accountability for every message
Each stored message records:
- the credential that submitted it,
- whether the submission arrived over TLS,
- sender, recipient, and subject,
- the full SMTP transcript of every delivery attempt.
Support conversations therefore start from facts: which key sent what, and
how the remote server answered.
Signing key protection
A private signer key and a webhook signing secret must stay secret. relay
generates each one at creation, encrypts it with Fernet under the platform
KMS key, and stores only the ciphertext. The database never contains either
in plain form.
DKIM public keys are public by design and appear in DNS. Webhook signing
secrets appear in the dashboard in whsec_ format for you to verify
deliveries. See
Webhooks for the
verification procedure.
Transport security
relay accepts submissions only over TLS, on port 465 (implicit TLS) or 587
(STARTTLS enforced before any other exchange). relay delivers through
STARTTLS and enforces MTA-STS for recipient domains that publish a policy.
The dashboard records whether each message traveled over TLS, so silent
downgrades do not exist.
Inbound protections
The MX server runs a sequence of gates before a message reaches your
webhook:
flowchart TD
A[Message arrives over STARTTLS] --> B{Recipient domain registered here?}
B -- No --> C[Reject with 550]
B -- Yes --> D{Inbound DMARC disposition}
D -- reject --> C
D -- quarantine --> E[Store as quarantined]
D -- none --> F[Store as received]
E --> G[Spam and malware scan]
F --> G
G -- score 15 or higher, or malware found --> H[Quarantine, no webhook]
G -- clean and not already quarantined --> I[Dispatch webhooks]
I --> J{Recipient is postmaster?}
J -- Yes --> K[Submit a copy from the receiving domain]
A reject disposition returns an SMTP failure to the sending server inside the
SMTP transaction. The message never enters the platform. A high spam score
quarantines the message instead of delivering it, and no webhook fires. The
same applies to malware and to any other message the scan rejects, and a
quarantined message is never forwarded. relay also forwards mail addressed to
postmaster, with or without a + extension, to every member of the
organization. Those copies are outgoing messages relay signs for the receiving
domain, so they are billed and listed like any other message the organization
sends. The dashboard shows every quarantined message with its score. The message
detail page renders an HTML body in a sandboxed frame that runs no scripts
and sends no referrer, so a body from the outside cannot act inside your
session. Remote images stay allowed by default, so the body looks as the
sender wrote it. A client preview shows the message the way that client
shows it. Outlook holds the remote images back, Gmail and Apple Mail show
them, and the preview loads them from the reader’s browser rather than
through a client’s image proxy. An image that travels inside the message is
no fetch at all.
Content Security Policy
Every web response carries an enforced Content Security Policy and a strict
integrity policy. Pages load scripts from relay alone, and every module is
pinned to a hash, so a browser refuses a script it cannot verify.
Node configuration and cluster keys
Talos, the node’s operating system, takes its first-boot configuration from the
Hetzner instance metadata service, which stores it as the server’s user data and
serves it back to anything running on the node at 169.254.169.254. That
document carries the cluster’s private keys: the machine and cluster bootstrap
tokens, the machine, etcd, API-server and aggregator CA keys, the etcd secretbox
key, and the service-account key. Caddy terminates untrusted TLS, dnsdist
answers DNS, and rspamd and the application parse mail, all on that node, and the
cluster network enforces no policy between pods. Code execution in a relay pod
therefore reads the cluster’s permanent PKI and can use it to reach the cluster
itself.
This is an accepted risk, not a control. The alternative is a hand-rolled ISO
install that keeps the configuration off the metadata service, which gives up the
declared, repeatable provisioning the platform is built on. The keys are per
cluster; replacing them means generating a new secrets bundle and rebuilding the
node.
Monitoring and secrets
All relay processes report errors and logs to Sentry, and reporting is off by
default. Error reports carry no message bodies, no tokens, and no credentials.
Logs are the same operational messages the services write to stdout, so they
can contain sender and recipient addresses and IP addresses. Message bodies do
not travel to Sentry. relay reads secrets from its environment and never writes
them to the database. Never send secrets inside email messages. Use the
dashboard’s credential management instead.
Related pages
- Encryption. TLS on
every hop and key material at rest. - Data privacy.
Hosting, retention, and data minimization. - Webhooks. Delivery
verification code examples.