# Email DNS

Canonical: https://learndns.org/learn/7-2-email-dns/

> MX, SPF, DKIM, and DMARC: the DNS records that decide whether a message reaches the inbox or is rejected outright as spam.

Email is, surprisingly, one of the most DNS-dependent services on the internet, relying heavily on the [MX and TXT record types](/learn/2-3-record-types/) covered in Part 2. When you send an email to `user@example.com`, the sending server doesn't magically know where to deliver it. It asks DNS. And modern email authentication — the system that prevents your domain from being spoofed — is built entirely on DNS TXT records.

Get this wrong, and your emails go to spam. Or worse — someone sends phishing emails *as* your domain, and you never know until your customers complain.

## MX Records: Email Routing

MX (Mail Exchange) records tell sending mail servers where to deliver email for your domain. They're conceptually simple but have important nuances.

```dns
example.com.    IN MX    10 mail1.example.com.
example.com.    IN MX    20 mail2.example.com.
example.com.    IN MX    30 mail3.example.com.
```

The number before the hostname is the **priority** (sometimes called "preference"). Lower numbers mean higher priority. In the example above, senders will try `mail1` first. If it's unavailable, they'll try `mail2`, then `mail3`.

### How MX Lookup Works

When `alice@sender.com` sends email to `bob@example.com`, the [DNS resolution process](/learn/2-2-resolution-process/) kicks in:

1. Sender's mail server queries `example.com` for MX records
2. DNS returns the MX records sorted by priority
3. Sender connects to the highest-priority (lowest number) server via SMTP on port 25
4. If that server is down, the sender tries the next priority level
5. If all MX servers are unreachable, the sender queues the message and retries (typically for up to 5 days)

```bash
# Check MX records for a domain
$ dig example.com MX +short
10 mail1.example.com.
20 mail2.example.com.

# Verify the mail server has an A record
$ dig mail1.example.com A +short
203.0.113.25
```

### MX Record Rules

- **MX targets must be A/AAAA records**, not CNAMEs. RFC 2181 §10.3 and RFC 5321 §5.1 forbid pointing MX records at CNAMEs. While some servers tolerate it, many don't.
- **Multiple records with the same priority** enable round-robin load balancing
- **A domain with no MX records** falls back to the A record of the domain itself (RFC 5321 §5.1), but don't rely on this — always set MX records explicitly
- **A "null MX"** (`0 .`) indicates the domain doesn't accept email (RFC 7505):

```dns
example.com.    IN MX    0 .
```

### Google Workspace MX Setup

The most common MX configuration you'll encounter:

```dns
example.com.    IN MX    1  aspmx.l.google.com.
example.com.    IN MX    5  alt1.aspmx.l.google.com.
example.com.    IN MX    5  alt2.aspmx.l.google.com.
example.com.    IN MX    10 alt3.aspmx.l.google.com.
example.com.    IN MX    10 alt4.aspmx.l.google.com.
```

## SPF: Sender Policy Framework

SPF lets you declare which servers are authorized to send email on behalf of your domain. Receiving servers check this to detect forgery.

SPF is published as a DNS TXT record on your domain:

```dns
example.com.    IN TXT    "v=spf1 include:_spf.google.com include:sendgrid.net ip4:203.0.113.0/24 -all"
```

Let's break this down:

| Component | Meaning |
|-----------|---------|
| `v=spf1` | SPF version (always `spf1`) |
| `include:_spf.google.com` | Also authorize Google's mail servers |
| `include:sendgrid.net` | Also authorize SendGrid's servers |
| `ip4:203.0.113.0/24` | Authorize this IP range |
| `-all` | Reject everything else (hard fail) |

### SPF Mechanisms

```
ip4:203.0.113.50      – Match specific IPv4 address
ip4:203.0.113.0/24    – Match IPv4 range
ip6:2001:db8::/32     – Match IPv6 range
a                      – Match domain's A records
mx                     – Match domain's MX servers
include:other.com      – Include other.com's SPF
exists:bl.example.com  – Match if DNS lookup succeeds
```

### SPF Qualifiers

The character before a mechanism changes how matches are treated:

```
+all    – Pass (allow). The "+" is default and usually omitted.
-all    – Hard fail (reject). Recommended for most domains.
~all    – Soft fail (accept but mark). Used during testing.
?all    – Neutral (no policy). Effectively useless.
```

### The 10-Lookup Limit

SPF has a critical limitation: **a maximum of 10 DNS lookups** during evaluation. Each `include`, `a`, `mx`, and `redirect` counts as a lookup, and included records can trigger additional lookups.

```
v=spf1 include:_spf.google.com       → 1 lookup (+ nested lookups)
       include:sendgrid.net           → 1 lookup (+ nested lookups)
       include:mailchimp.com          → 1 lookup (+ nested lookups)
       include:zendesk.com            → 1 lookup (+ nested lookups)
       ...
```

Exceeding 10 lookups causes a `permerror`, and your SPF record is ignored entirely. As you add more email services, this limit becomes a real problem.

**Solutions:**
- **Flatten your SPF** — replace `include` statements with the resolved IP ranges. Tools like `spflattener.com` help automate this.
- **Use subdomains** — send marketing email from `mail.example.com` with its own SPF record
- **Use `ip4`/`ip6` directly** — these don't count toward the lookup limit

## DKIM: DomainKeys Identified Mail

DKIM adds a cryptographic signature to outgoing emails, proving the message hasn't been tampered with and was authorized by the domain owner. (For a different kind of DNS cryptographic authentication, see [DNSSEC](/learn/4-2-dnssec/).)

### How DKIM Works

1. You generate a public/private key pair
2. The **private key** stays on your mail server and signs outgoing messages
3. The **public key** is published in DNS so receivers can verify signatures

```
Sending Server                      Receiving Server
     │                                    │
     │  1. Sign email with private key    │
     │  2. Add DKIM-Signature header      │
     │──────────── email ────────────────>│
     │                                    │  3. Extract selector from header
     │                                    │  4. Query DNS for public key
     │<──── DNS query ───────────────────│
     │──── TXT record (public key) ─────>│
     │                                    │  5. Verify signature
     │                                    │  6. Pass or fail
```

### DKIM DNS Record

DKIM records are TXT records at a specific subdomain: `{selector}._domainkey.example.com`

```dns
google._domainkey.example.com.  IN TXT  "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC..."
```

The **selector** (here, `google`) allows multiple DKIM keys per domain — one for Google Workspace, another for SendGrid, etc.

```bash
# Check a DKIM record
$ dig google._domainkey.example.com TXT +short
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4..."
```

### DKIM Record Components

| Tag | Meaning | Example |
|-----|---------|---------|
| `v` | Version | `DKIM1` |
| `k` | Key type | `rsa` (default), `ed25519` |
| `p` | Public key (base64) | `MIGfMA0GCS...` |
| `t` | Flags | `y` (testing mode), `s` (strict) |

### Rotating DKIM Keys

DKIM keys should be rotated periodically (every 6–12 months). The process:

1. Generate a new key pair with a new selector (e.g., `google2024`)
2. Publish the new public key in DNS
3. Wait for DNS propagation
4. Configure your mail server to sign with the new key
5. Keep the old key in DNS for a few days (receivers may still verify old messages)
6. Remove the old DNS record

## DMARC: Tying It All Together

DMARC (Domain-based Message Authentication, Reporting, and Conformance) builds on SPF and DKIM to create a complete authentication policy. It tells receiving servers what to do when SPF and DKIM checks fail — and sends you reports about authentication results.

### DMARC DNS Record

DMARC is published as a TXT record at `_dmarc.example.com`:

```dns
_dmarc.example.com.  IN TXT  "v=DMARC1; p=reject; rua=mailto:dmarc@example.com; ruf=mailto:dmarc-forensic@example.com; pct=100; adkim=s; aspf=s"
```

| Tag | Meaning | Values |
|-----|---------|--------|
| `v` | Version | `DMARC1` |
| `p` | Policy | `none` (monitor), `quarantine` (spam folder), `reject` (bounce) |
| `rua` | Aggregate report address | `mailto:dmarc@example.com` |
| `ruf` | Forensic report address | `mailto:dmarc-forensic@example.com` |
| `pct` | Percentage of messages to apply policy | `0`–`100` |
| `adkim` | DKIM alignment | `r` (relaxed), `s` (strict) |
| `aspf` | SPF alignment | `r` (relaxed), `s` (strict) |

### DMARC Alignment

DMARC introduces the concept of **alignment** — the `From:` header domain must match either the SPF-authenticated domain or the DKIM-signing domain.

```
Email From: user@example.com
SPF authenticated: example.com        → SPF aligned ✅
DKIM signed by: example.com           → DKIM aligned ✅
DMARC result: PASS
```

```
Email From: user@example.com
SPF authenticated: bounce.sendgrid.net → SPF NOT aligned ❌
DKIM signed by: example.com            → DKIM aligned ✅
DMARC result: PASS (only one needs to align)
```

### The DMARC Rollout Path

Don't jump straight to `p=reject`. Follow this gradual rollout:

```dns
; Phase 1: Monitor (weeks 1-4)
_dmarc.example.com.  IN TXT  "v=DMARC1; p=none; rua=mailto:dmarc@example.com"

; Phase 2: Quarantine a percentage (weeks 5-8)
_dmarc.example.com.  IN TXT  "v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc@example.com"

; Phase 3: Quarantine all (weeks 9-12)
_dmarc.example.com.  IN TXT  "v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@example.com"

; Phase 4: Reject (after confirming all legitimate sources pass)
_dmarc.example.com.  IN TXT  "v=DMARC1; p=reject; rua=mailto:dmarc@example.com"
```

## Common Email DNS Mistakes

**1. No SPF or DMARC at all.** Even if you don't send email from a domain, set these up. Otherwise, spammers can impersonate you.

```dns
; "I don't send email" configuration
example.com.    IN TXT    "v=spf1 -all"
example.com.    IN MX     0 .
_dmarc.example.com.  IN TXT  "v=DMARC1; p=reject"
```

**2. Multiple SPF records.** A domain must have at most one SPF TXT record. Multiple records cause a `permerror`:

```dns
; ❌ WRONG — two SPF records
example.com.  IN TXT  "v=spf1 include:_spf.google.com -all"
example.com.  IN TXT  "v=spf1 include:sendgrid.net -all"

; ✅ CORRECT — single combined record
example.com.  IN TXT  "v=spf1 include:_spf.google.com include:sendgrid.net -all"
```

**3. SPF `+all`.** This literally means "anyone can send email as my domain." Never use it.

**4. DKIM key too short.** Use at least 2048-bit RSA keys. 1024-bit keys are considered weak.

**5. Forgetting to set up DKIM for each sending service.** If you use Google Workspace *and* SendGrid *and* Mailchimp, each needs its own DKIM configuration.

## Testing Email Deliverability

After configuring everything, verify your setup:

```bash
# Check SPF
$ dig example.com TXT +short | grep spf
"v=spf1 include:_spf.google.com -all"

# Check DKIM
$ dig google._domainkey.example.com TXT +short
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3..."

# Check DMARC
$ dig _dmarc.example.com TXT +short
"v=DMARC1; p=reject; rua=mailto:dmarc@example.com"

# Check MX
$ dig example.com MX +short
1 aspmx.l.google.com.
5 alt1.aspmx.l.google.com.
```

**Online tools for comprehensive testing:**
- **mail-tester.com** — Send a test email and get a deliverability score
- **MXToolbox.com** — SPF, DKIM, DMARC, and blacklist checks
- **Google Postmaster Tools** — Monitor your domain's reputation with Gmail
- **dmarcian.com** — DMARC report analysis

## Key Takeaways

- MX records route email; always set them explicitly, even for domains that don't receive email
- SPF declares authorized senders — watch the 10-lookup limit
- DKIM cryptographically signs messages — rotate keys periodically
- DMARC ties SPF and DKIM together with enforcement policies
- Roll out DMARC gradually: `none` → `quarantine` → `reject`
- Always configure email DNS, even for domains that don't send email — protect against spoofing