Transport Security

Operator guide to Sealed Mail's transport hardening: requiring TLS for inbound delivery, publishing MTA-STS, outbound TLS and DANE posture, TLS-RPT, and secrets at rest.

Sealed Mail's end-to-end encryption protects message contents between Owlat workspaces. This page covers the complementary layer: hardening the SMTP transport so that mail to and from ordinary (non-Owlat) mail servers is encrypted in flight and can't be silently downgraded, plus the secrets that keep everything sealed at rest.

Inbound delivery is secure by default: Owlat rejects SMTP transactions that have not upgraded with STARTTLS. The outbound and publication controls remain deliberate operator choices.

Requiring TLS for inbound delivery

Settings → Sealed Mail → Require TLS for incoming mail controls the SMTP acceptance floor. It is on by default. While enabled, the MTA checks the live connection before accepting MAIL FROM; a plaintext sender receives 550 5.7.10 Encryption needed and Owlat accepts or stores none of the message bytes.

Configure BOUNCE_TLS_CERT and BOUNCE_TLS_KEY so the inbound SMTP server can advertise STARTTLS. Without a certificate and key, the default-on floor rejects every inbound delivery because senders have no encrypted path; only disable the floor as a temporary recovery measure.

Owners and admins can disable the floor for a legacy sender that cannot negotiate STARTTLS. Disabling it permits plaintext delivery to every address on the instance, so it should be a temporary compatibility measure. The choice is synchronized to the MTA's Redis-backed SMTP gate; missing or unreadable policy state fails closed and continues to require TLS.

This acceptance floor requires encryption, not certificate authentication. Publishing MTA-STS in enforce mode complements it by telling compatible senders in advance to require a certificate-verified TLS connection to the expected MX.

Publishing MTA-STS (inbound)

MTA-STS (RFC 8461) lets senders that deliver mail to your deployment require encrypted, certificate-verified delivery — protecting against STARTTLS-stripping attacks. You publish it from Delivery → provider config ("Inbound TLS policy (MTA-STS)"), which is owner/admin-gated. The posture is stored on the workspace settings as mtaStsMode and has three steps, in ascending strictness:

ModeUI labelEffect
noneOffNo policy is published. Senders deliver mail to you exactly as they do today. The default.
testingTestingA policy is published, but senders only report TLS problems — they never fail delivery. The safe first step while you watch for issues.
enforceEnforceSenders must deliver over verified TLS to a listed MX, or the message is rejected. Turn this on only once testing looks clean.

Publishing a policy has two DNS/HTTPS halves, both surfaced on the Domains page once you leave none:

  1. A TXT record at _mta-sts.<domain> announcing the policy and a short id that changes whenever the policy changes (so senders re-fetch).
  2. The policy file itself, served over HTTPS at https://mta-sts.<domain>/.well-known/mta-sts.txt. Owlat serves this automatically for a configured mail host; you just publish the mta-sts DNS record that points to it.

The published policy uses a one-week max_age (the widely-used default). Start at testing, watch the TLS-RPT dashboard until reports look clean, then advance to enforce.

enforce with no inbound mail host configured can't actually take effect — there's no MX to serve a verified-TLS policy for. Set up receiving first, or stay on testing. The UI warns you honestly in this case.

Outbound TLS posture (OUTBOUND_TLS_MODE)

OUTBOUND_TLS_MODE governs the built-in MTA's direct-to-MX delivery — how strict Owlat is about TLS when it sends mail out. It's one of three values (default opportunistic):

OUTBOUND_TLS_MODEBehaviour
opportunistic (default)Encrypt when the receiver offers STARTTLS, but never fail delivery on a missing or unverifiable certificate (RFC 7435). Byte-identical to the historic behaviour.
requireThe handshake must upgrade to TLS; the certificate is not verified (encrypt-always, tolerate self-signed MX certs).
require-verifiedThe handshake must upgrade to TLS and the certificate must verify against the WebPKI trust store. Can bounce mail to receivers with broken or self-signed TLS.

The MTA reads this from its own config; the value is also surfaced read-only to the delivery transport editor so re-applying an edit preserves your chosen floor. A typo (for example require_verified with an underscore) fails the MTA boot fast rather than silently degrading to opportunistic.

Independently of this global floor, Owlat already enforces other domains' published MTA-STS policies when it sends to them, so a receiver that demands verified TLS gets it regardless of your outbound mode.

DANE posture (DANE_MODE)

DANE (RFC 7672) authenticates a receiver's TLS certificate against a DNSSEC-signed TLSA record, closing the trust gap that plain WebPKI leaves. It's governed by DANE_MODE on the MTA, one of three values:

DANE_MODEBehaviour
offNo TLSA lookups, no DANE effect — byte-identical to the historic (DANE-disabled) path.
reportLook up each recipient MX's TLSA RRset and check the certificate against it, then report the result in TLS-RPT (a success, or a validation-failure under the tlsa policy) — but never require TLS or bounce on a DANE outcome. Delivery proceeds on the normal opportunistic/MTA-STS decision. Observability only, zero delivery impact.
enforceA usable TLSA RRset mandates verified TLS authenticated against it (this supersedes MTA-STS); a non-matching certificate defers the message (never falls back to cleartext) and records a validation-failure.

The default is off, matching locked decision D6. Opt into report to gather DANE visibility in the TLS-RPT dashboard without changing delivery outcomes. Promote a domain to enforce only once report-only shows its TLSA records validating cleanly — exactly the testingenforce progression you use for MTA-STS.

DANE needs a validating DoH (DNS-over-HTTPS, RFC 8484) resolver, configured with DANE_RESOLVER_URL:

Env varPurpose
DANE_MODEoff (default), report, or enforce. An unrecognised value fails the MTA boot fast rather than silently degrading to a different posture.
DANE_RESOLVER_URLA validating DoH resolver endpoint. Needed for report/enforce to actually run.
No resolver → DANE is inert

report and enforce both require DANE_RESOLVER_URL. When it is unset, DANE is inert in every mode (no TLSA lookups). Set both DANE_MODE=report and DANE_RESOLVER_URL to turn report-only observability on.

DANE's entire security rests on the resolver's DNSSEC AD (Authenticated Data) bit, so the channel to the resolver must be one an on-path attacker can't forge over. When a resolver URL is set, the MTA validates it at boot (in every mode, so a mistake is caught before you enable DANE, not on enable-day):

  • A malformed DANE_RESOLVER_URL → boot fails.
  • A non-https: remote resolver → boot fails. http: is permitted only for a loopback resolver (localhost, 127.0.0.1, ::1, *.localhost), where there is no on-path network to attack.

When DANE is enabled, the same validating resolver performs destination discovery as well as the TLSA lookup. The MTA retains the DNSSEC state of the MX and address answers, refuses to apply DANE through an unauthenticated address chain, and treats resolver failures as temporary delivery failures in enforce mode instead of silently retrying without DANE.

Both interoperable SMTP usages are supported. DANE-EE(3) authenticates the leaf key/certificate directly, so WebPKI trust, certificate names, and certificate dates are intentionally ignored. DANE-TA(2) validates the presented chain to the TLSA-associated CA and still enforces certificate validity, CA/signature chaining, and the RFC 7672 MX reference-name rules. During certificate rotation, any valid association in a mixed EE/TA RRset may authenticate the peer. Reusable SMTP connections are partitioned by a fingerprint of the complete TLSA RRset and its reference names, so a connection authenticated under an older policy is never reused under a changed policy.

Run a local validating resolver

The recommended setup (per D6) is a local validating resolver — for example https://127.0.0.1:8443/dns-query — rather than a public DoH endpoint. A local resolver you control does its own DNSSEC validation, so the AD bit it returns is trustworthy end to end. Trusting a remote resolver's AD bit means trusting both that resolver and the network path to it.

Reading the TLS-RPT dashboard

TLS Reporting (TLS-RPT, RFC 8460) is how you close the loop: other mail servers send you daily aggregate reports about how TLS negotiation went when they delivered to you. Owlat ingests them and rolls them up on the Delivery → provider config page ("Inbound TLS reports — How partners reach us over TLS").

To receive reports you must publish a _smtp._tls TXT record naming a reporting address you actually monitor. Set MTA_TLSRPT_RUA (for example mailto:tls-reports@example.com or an https:// collector) and Owlat emits the record — v=TLSRPTv1; rua=<address>. If MTA_TLSRPT_RUA is unset, no _smtp._tls record is generated (Owlat doesn't provision a per-domain reports mailbox, so the reports would otherwise go unread).

The dashboard shows, over a rolling 30-day window:

  • An overall TLS success rate, plus a breakdown by reporting organization.
  • A plain-language tally of any failures — for example "STARTTLS stripped upstream" or "certificate didn't match the server name".

Ingestion is idempotent by reporting organization and report-id, so a re-delivered report never double-counts. Watch this dashboard while a domain is on MTA-STS testing before you promote it to enforce.

Secrets at rest

Two secrets protect Sealed Mail's data at rest. Both are set once and must be kept safe — losing them has real consequences.

MTA_SECRET

Seals the MTA's own credentials at rest: DKIM signing keys and relay credentials. The MTA fails to boot if it's absent or too weak, rather than sealing under a guessable key. Scope it to the MTA process.

INSTANCE_SECRET

The root secret for everything Owlat seals at rest on the Convex side:

  • The Sealed Mail key vault — every sealing private key is encrypted under a domain-separated box derived from INSTANCE_SECRET.
  • Message bodies at rest — stored bodies and blobs are sealed under separate domain-separated keys derived from the same secret (see Sealed Mail at Rest).
Losing INSTANCE_SECRET

INSTANCE_SECRET opens the key vault. If you lose it and every recovery kit is lost, sealed mail already received can no longer be opened — there is no admin escrow (D7). Back it up as carefully as any root credential.

Rotating INSTANCE_SECRET

To rotate the instance secret without losing access:

  1. Set the new value as INSTANCE_SECRET and keep the previous value in INSTANCE_SECRET_PREVIOUS. While both are set, the vault opens each sealed key under the current secret and falls back to the previous one, so reads keep working mid-migration.
  2. Run Re-seal sealed mail from Settings → Sealed Mail ("After changing the instance secret"). This re-encrypts every stored key under the new secret. Sealed mail keeps opening throughout.
  3. Once re-sealing finishes, remove INSTANCE_SECRET_PREVIOUS.