> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oximail.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Email authentication & security

> The DNS records that authorize, authenticate, and secure your mail: MX, PTR, SPF, DKIM, DMARC, MTA-STS, TLS-RPT, DANE, and the autodiscovery set. Provision them with oximail setup and verify them with oximail check-dns.

Deliverability is decided in DNS before a single byte of mail moves. A receiving server looks up your domain, asks *who is allowed to send for it*, *is this message intact and authentic*, and *can I reach you over enforced TLS* — and answers those questions entirely from records you publish. This page is the operator's DNS contract: what to publish for a mail domain, why each record exists, how OxiMail generates and serves it, and how to verify the result.

Two commands frame the whole workflow:

* **`oximail setup`** provisions the full record set. With `--cloudflare-token` it writes the records straight into the zone through the Cloudflare API (idempotent — an existing record is left in place). Without a token it tells you to create the records by hand.
* **`oximail check-dns`** performs live lookups and scores the domain. It queries through Cloudflare (`1.1.1.1` / `1.0.0.1`) with DNSSEC validation **on**, deliberately bypassing stale local caches and split-horizon resolvers that mislead during setup. The report ends with `n/n -- Ready for production` or `n/n -- Fix the items above`.

<Note>
  Run `oximail check-dns` from the server after every DNS change. DNSSEC is enforced on the TXT lookups, so a zone with broken DNSSEC surfaces as a hard error rather than silently accepting forged records.
</Note>

## The record set

For a mail domain `example.com` served by the host `mail.example.com` at IP `203.0.113.10`, the full set OxiMail provisions is:

| Record                  | Name                                                                                                                     | Value                                                          | Purpose                                                                                  |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **MX**                  | `example.com`                                                                                                            | `mail.example.com` (priority 10)                               | Where the world delivers your mail. A backup MX uses priority 20.                        |
| **A**                   | `mail.example.com`                                                                                                       | `203.0.113.10`                                                 | The mail host's address.                                                                 |
| **SPF**                 | `example.com` (TXT)                                                                                                      | `v=spf1 ip4:203.0.113.10 -all`                                 | Authorizes your IP to send, hard-fails everyone else.                                    |
| **DKIM**                | `default._domainkey.example.com` (TXT)                                                                                   | `v=DKIM1; k=rsa; p=<public key>`                               | The public key receivers use to verify your signature.                                   |
| **DMARC**               | `_dmarc.example.com` (TXT)                                                                                               | `v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com` | What to do with mail that fails SPF and DKIM alignment, and where to send reports.       |
| **MTA-STS**             | `_mta-sts.example.com` (TXT)                                                                                             | `v=STSv1; id=<YYYYMMDD>`                                       | Announces that you publish an MTA-STS policy.                                            |
| **MTA-STS policy host** | `mta-sts.example.com` (CNAME)                                                                                            | `mail.example.com`                                             | Where the policy file is served (`/.well-known/mta-sts.txt`).                            |
| **TLS-RPT**             | `_smtp._tls.example.com` (TXT)                                                                                           | `v=TLSRPTv1; rua=mailto:tls-reports@example.com`               | Where receivers report TLS connection failures.                                          |
| **CAA**                 | `example.com`                                                                                                            | issuance restricted to `letsencrypt.org`                       | Restricts which CA may issue certificates for the domain.                                |
| **autoconfig**          | `autoconfig.example.com` (CNAME)                                                                                         | `mail.example.com`                                             | Thunderbird-style client auto-configuration.                                             |
| **autodiscover**        | `autodiscover.example.com` (CNAME)                                                                                       | `mail.example.com`                                             | Outlook-style client auto-configuration.                                                 |
| **SRV**                 | `_submission._tcp` (587), `_imaps._tcp` (993), `_caldavs._tcp` (443), `_carddavs._tcp` (443), `_autodiscover._tcp` (443) | priority/weight + port → `mail.example.com`                    | Lets clients discover the submission, IMAP, CalDAV, CardDAV, and autodiscover endpoints. |

The sections below explain the records that carry real policy. The MX, A, and SRV records are mechanical — publish them and verify with `check-dns`.

## SPF

OxiMail publishes a strict, single-source SPF record:

```text theme={null}
example.com.  TXT  "v=spf1 ip4:203.0.113.10 -all"
```

`ip4:203.0.113.10` authorizes exactly your mail host; `-all` is a **hard fail** — any other IP claiming to send as `example.com` is rejected outright. This is the right default for a single self-hosted server.

Adjust it by hand if your topology is wider:

* **Send from more than one IP** (a backup MX host, a separate relay): add each `ip4:` / `ip6:` mechanism, e.g. `v=spf1 ip4:203.0.113.10 ip4:203.0.113.20 -all`.
* **Relay through a third party** (a transactional provider): add their published `include:`, e.g. `include:_spf.example-relay.com`.
* **IPv6 senders**: SPF only authorizes what it lists. If your MX also sends over IPv6, add the matching `ip6:` mechanism, otherwise IPv6 mail hard-fails SPF at the receiver.

<Warning>
  `check-dns` evaluates SPF by direct IP presence and `+all` only; it does not recurse into `include:` or `redirect=`. If you rely on an `include:`, verify alignment with an external SPF validator as well.
</Warning>

## DKIM

DKIM signs each outgoing message with a private key; receivers fetch the public key from DNS and verify the signature. OxiMail signs **at delivery time** in the outbound worker (see [the SMTP layer](../architecture/oximail-smtp)).

Generate the key and read back the record to publish:

```bash theme={null}
oximail setup dkim --domain example.com --selector default
```

This writes the private key to `/etc/oximail/dkim/example.com.default.key` and prints the matching DNS record:

```text theme={null}
default._domainkey.example.com.  TXT  "v=DKIM1; k=rsa; p=MIGfMA0GCSqG..."
```

* **Selector** is `default`. The DNS name is always `<selector>._domainkey.<domain>`, so a rotation publishes a *new* selector alongside the old one, lets both verify during the cutover, then retires the old record.
* **Algorithm** defaults to RSA (`k=rsa`). The full setup wizard publishes the RSA record automatically when run with a Cloudflare token.
* **Oversigning.** The signature's `h=` list names `From`, `To`, and `Subject` one extra time, so a downstream actor cannot inject a second copy of a display-critical header without invalidating the signature (RFC 6376 §8.15). No operator action needed; it is how OxiMail signs.

**On the inbound side, `dkim=fail` says why.** The verification reason now travels alongside the verdict, into the `Authentication-Results` header **and** into the result stored per message — so a post-mortem can separate cases a bare verdict conflated, and which call for opposite actions:

| What failed                                | What it means for you                                                                               |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
| The signature is cryptographically invalid | An abuse signal. Nothing to fix on your side.                                                       |
| The body was altered in transit            | Usually a middlebox or list processor between the sender and you.                                   |
| No public key was found                    | The sender's DNS problem, not yours.                                                                |
| The signature is dated in the future       | **Your clock is behind.** See the clock note in [operations](./operations#when-something-is-wrong). |

The reason recorded is that of the first non-passing signature. A `pass` carries none, deliberately: a bad signature sitting next to a good one is normal — mailing lists re-sign — and `dkim=pass reason=…` would read as a defect where there is none.

<Warning>
  When you add a mail domain **after** the initial setup, generate and publish its DKIM record explicitly. A domain whose key exists but whose `default._domainkey` TXT record is missing will sign mail that receivers then reject as `dkim=fail` (no key found). Run `oximail check-dns` for each domain to confirm the DKIM check passes.
</Warning>

## DMARC

DMARC tells receivers what to do when a message fails **both** SPF and DKIM alignment, and where to send aggregate reports. OxiMail's default is:

```text theme={null}
_dmarc.example.com.  TXT  "v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com"
```

`p=quarantine` routes failing mail to the recipient's Junk folder. Tune the policy to your rollout stage:

* **`p=none`** — monitor only. Publish this first if you are unsure your SPF and DKIM are correct; read the `rua` aggregate reports, then tighten.
* **`p=quarantine`** — the OxiMail default. Failing mail is junked, not refused.
* **`p=reject`** — failing mail is refused at SMTP time. On the receiving side, OxiMail honours a sender's `p=reject` with a `550 5.7.26` rejection (see [the SMTP layer](../architecture/oximail-smtp)).

Make sure the `rua` mailbox exists and is monitored — it is where you find out a legitimate source is failing before recipients stop seeing your mail.

## MTA-STS and TLS-RPT

MTA-STS (RFC 8461) lets you *require* that other servers use validated TLS when they deliver to you, closing the downgrade-to-cleartext window that plain opportunistic STARTTLS leaves open. It has three moving parts, and OxiMail serves the policy itself:

1. The **TXT record** `_mta-sts.example.com` = `v=STSv1; id=<YYYYMMDD>` announces that a policy exists. The `id` changes whenever the policy changes.
2. The **policy host** `mta-sts.example.com` (a CNAME to your mail host) is where senders fetch the policy over HTTPS.
3. The server serves `/.well-known/mta-sts.txt` from the `[mta_sts]` configuration block:

```toml theme={null}
[mta_sts]
mode = "enforce"        # "enforce" | "testing" | "none" (default: enforce)
mx = "mail.example.com"
max_age = 604800        # policy cache lifetime in seconds (default: 7 days)
```

* **`enforce`** — senders that honour MTA-STS refuse to deliver to you over unvalidated TLS.
* **`testing`** — policy is published but failures are reported, not enforced. Use this while validating, then switch to `enforce`.
* **`none`** — withdraw the policy.

**TLS-RPT** (RFC 8460) is the companion that gives you visibility:

```text theme={null}
_smtp._tls.example.com.  TXT  "v=TLSRPTv1; rua=mailto:tls-reports@example.com"
```

Receivers send daily reports of TLS connection failures to the `rua` address, which is how you detect a misconfigured policy or an active downgrade attempt. As with DMARC, monitor that mailbox.

## DANE (TLSA)

DANE (RFC 7672) is the DNSSEC-anchored alternative to MTA-STS: a `TLSA` record under `_25._tcp.mail.example.com` pins your certificate so a sender validates TLS against DNS rather than a public CA list. It requires a **DNSSEC-signed zone**.

OxiMail enforces DANE on the **outbound** path by default and keeps your **inbound** TLSA records fresh automatically after each certificate renewal. Because a renewed certificate changes the pinned value, this requires a configured DNS provider:

```toml theme={null}
[smtp]
dane_enabled = true     # default

[dns]
# Required when dane_enabled = true. Values: "cloudflare" | "rfc2136" | "manual"
provider = "cloudflare"
cloudflare_api_token = "your-api-token"
```

<Warning>
  When `dane_enabled = true` (the default), `[dns] provider` **must** be set or the server refuses to start (fail-loud, ADR-027). Without a provider, an ACME renewal would silently leave your TLSA records stale and break inbound mail for every DANE-validating sender.
</Warning>

The provider options:

* **`cloudflare`** — auto-publishes the new TLSA records via the Cloudflare API after each ACME renewal. Recommended.
* **`manual`** — you update TLSA records out of band. The renewal logs a warning reminding you to do it.
* **`rfc2136`** — a v1 stub that currently behaves like `manual` (logs the same warning); the dynamic-update implementation is deferred.

See [TLS & ACME](./tls-acme) for certificate provisioning and renewal.

## Reverse DNS (PTR) and deliverability

Reverse DNS is published by whoever controls the IP block (your hosting provider — Hetzner, OVH, AWS, …), not in your domain's zone. It is one of the strongest reputation signals a receiver has.

* Set the **PTR** for `203.0.113.10` to `mail.example.com` — the same name your server uses in its SMTP `HELO`/`EHLO`. Many large receivers reject or heavily penalize a mismatch (no FCrDNS).
* `check-dns` passes the PTR check when the record resolves to the mail domain or a subdomain of it; for best results make it resolve **exactly** to your mail host and confirm the forward `A` record points back to the same IP.
* New IPs have no sending reputation. Warm up gradually and keep volume steady; a sudden spike from a cold IP looks like a compromised host.

## Autodiscovery

Mail clients find your endpoints from the **SRV** records and the `autoconfig` / `autodiscover` CNAMEs in the table above. The full `oximail setup` run also publishes a **CAA** record restricting certificate issuance to Let's Encrypt, which both hardens the domain and matches OxiMail's ACME setup. None of these carry tunable policy — publish them and let `check-dns` confirm.

## Verifying with oximail check-dns

```bash theme={null}
oximail check-dns --domain example.com --ip 203.0.113.10 --selector default
```

The report runs ten live checks — MX, rDNS (PTR), SPF, DKIM, DMARC, TLS-RPT, and the four SRV records — and prints a pass/fail line for each:

```text theme={null}
DNS Check Report for example.com
Expected IP: 203.0.113.10
DKIM selector: default
------------------------------------------------------------
✅ MX: Found: mail.example.com (priority 10)
✅ rDNS (PTR): PTR records: mail.example.com
✅ SPF: SPF record includes IP 203.0.113.10: v=spf1 ip4:203.0.113.10 -all
✅ DKIM: Found DKIM record at default._domainkey.example.com
✅ DMARC: Found: v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com
...
------------------------------------------------------------
10/10 -- Ready for production
```

The MTA-STS policy and DANE TLSA records are enforced by the server at send time and served from the policy host rather than re-checked here; validate those with an external MTA-STS / DANE checker after publishing.

Common failures:

* **PTR mismatch** — fix the reverse record at your hosting provider, not in your zone.
* **DKIM "no record"** — you generated the key but did not publish the TXT record. See the DKIM section above.
* **SPF "does not include IP"** — your sending IP is not listed; add the `ip4:` / `ip6:` mechanism.
* **Lookup failed** — a propagation delay (wait and re-run) or a broken DNSSEC zone (the lookup fails hard rather than trusting an unvalidated answer).

## Standards

* [RFC 7208](https://www.rfc-editor.org/rfc/rfc7208) — Sender Policy Framework (SPF).
* [RFC 6376](https://www.rfc-editor.org/rfc/rfc6376) — DomainKeys Identified Mail (DKIM).
* [RFC 7489](https://www.rfc-editor.org/rfc/rfc7489) — Domain-based Message Authentication, Reporting, and Conformance (DMARC).
* [RFC 8461](https://www.rfc-editor.org/rfc/rfc8461) — SMTP MTA Strict Transport Security (MTA-STS).
* [RFC 8460](https://www.rfc-editor.org/rfc/rfc8460) — SMTP TLS Reporting (TLS-RPT).
* [RFC 7672](https://www.rfc-editor.org/rfc/rfc7672) — SMTP Security via Opportunistic DANE TLS.

For how these records are consumed and enforced on the wire, see [the SMTP layer](../architecture/oximail-smtp); for certificate provisioning, see [TLS & ACME](./tls-acme); for the spam pipeline that authentication feeds, see [anti-spam](./anti-spam).
