The idea: who may send mail as bank.example?
Email was designed in 1982 with no proof of who sent a message. Any server can connect to any other server on port 25 and type From: alice@bank.example. Three DNS records added later let the owner of a domain say which mail is really theirs: SPF lists the servers allowed to send, DKIM publishes a public key that checks a signature on the message, and DMARC ties both to the From: address the reader sees and says what to do with mail that fails.
The page follows one mail from alice@bank.example to bob@inbox.example and then sends the same receiver mails from an attacker and from a forwarder. The three servers on the left use documentation addresses; the DNS box holds every record the page needs.
Finding the server: the MX record
To deliver to bob@inbox.example, the sending server takes the part after the @ and asks DNS for its MX (mail exchanger) records. Each one has a preference number and a host name: inbox.example MX 10 mx1.inbox.example. The sender tries the lowest number first, looks up its address (198.51.100.10) and opens a TCP connection to port 25. The lookup itself is the chain of resolvers shown on How DNS Resolution Works; here DNS is one box.
The SMTP session: envelope and headers
SMTP is a text dialogue. The receiver speaks first, and every command gets a three-digit reply: 2xx is OK, 3xx is go on, 4xx is try later, 5xx is a permanent no.
| Sender (C:) | Receiver (S:) | What it means |
|---|---|---|
220 mx1.inbox.example ESMTP | Greeting after the TCP connect | |
EHLO mail.bank.example | 250 | The sender names itself; the reply lists extensions |
MAIL FROM:<bounce@bank.example> | 250 2.1.0 OK | Envelope sender: where bounces go |
RCPT TO:<bob@inbox.example> | 250 2.1.5 OK | Envelope recipient: where the mail goes |
DATA | 354 | Send the message, end with a line holding one dot |
headers, body, . | 250 or 550 5.7.1 | Accepted, or refused for good |
QUIT | 221 | Close |
The key point: the envelope (MAIL FROM, RCPT TO) and the headers inside DATA (From:, To:) are separate, and SMTP never checks that they agree. A mail client shows the header From:. A forwarded mail is still addressed To: bob@alumni.example while its envelope says RCPT TO:<bob@inbox.example>.
SPF: is this IP allowed to send?
SPF (Sender Policy Framework) is a TXT record on the envelope domain: bank.example TXT "v=spf1 ip4:192.0.2.25 -all". It lists the addresses allowed to send for that domain, and -all says every other address fails (~all would be a soft fail). After MAIL FROM the receiver fetches the record of the domain in MAIL FROM and compares it with the IP of the TCP connection, which cannot be faked the way a header can.
SPF has two gaps. It checks the envelope domain, not the From: the reader sees: an attacker can pass SPF with MAIL FROM:<bounce@evil.example> from its own server (Demo 3). And it breaks when mail is forwarded, because the forwarder's IP is not in bank's list (Demo 4).
DKIM: a signature checked with a key from DNS
DKIM (DomainKeys Identified Mail) signs the message. The sending server hashes the body into bh=, then hashes the chosen headers (h=from:subject) together with bh, and signs that hash with its private key. The result goes into a DKIM-Signature header with the signing domain d=bank.example and a selector s=s1. The receiver fetches the public key at s1._domainkey.bank.example, recomputes both hashes and checks the signature.
The page uses toy RSA numbers, as on How Digital Signatures Work: n = 3233 = 61 × 53, e = 17, d = 2753, and a toy hash. The statement's body hash is bh = 5311, the header hash is h = 2178, and the signature is b = 21782753 mod 3233 = 1922. The receiver checks 192217 mod 3233 = 2178 = h. Real DKIM uses RSA-2048 or Ed25519 and SHA-256 (Demo 1).
A signature survives forwarding, because nothing it covers changes (Demo 4). It does not survive a mailing list that adds a footer: the body hash of what arrives is 8071, not 5311, and the check fails before the RSA step (Demo 5).
DMARC: alignment with the From: domain
SPF and DKIM each say "this mail is OK for some domain". DMARC asks the question the reader cares about: is it OK for the domain in From:? It passes only if SPF passes for a domain aligned with the From: domain, or DKIM passes with an aligned d=. Relaxed alignment (the default, aspf=r, adkim=r) accepts the same organizational domain, so mail.bank.example aligns with bank.example; strict alignment needs the exact name.
| SPF | DKIM | Aligned | DMARC | |
|---|---|---|---|---|
| Legit (Demo 1) | pass bank.example | pass d=bank.example | both | pass ✓ |
| Spoofed From: (Demo 2) | fail | none | neither | fail ✗ |
| Attacker's own domain (Demo 3) | pass evil.example | pass d=evil.example | neither | fail ✗ |
| Forwarded (Demo 4) | fail | pass d=bank.example | DKIM | pass ✓ |
| List footer (Demo 5) | fail | fail (body hash) | neither | fail ✗ |
Policy and reports
The record _dmarc.bank.example TXT "v=DMARC1; p=reject" tells receivers what the domain owner wants done with mail that fails. With p=reject the receiver answers 550 5.7.1 at the end of DATA and nothing is delivered. With p=quarantine it accepts the mail and files it as spam. With p=none it delivers as usual (Demo 6). A rua=mailto: tag asks receivers to send daily aggregate reports, so owners usually start at p=none, read the reports until every real sender passes, and then move to quarantine and reject. Since 2024 Gmail and Yahoo require bulk senders to publish DMARC.
Forwarding and mailing lists
Plain forwarding keeps the envelope sender, so SPF fails at the next hop; DKIM carries the mail through. Forwarders can also rewrite the envelope sender with SRS (Sender Rewriting Scheme), so SPF checks their own domain. Mailing lists that change the subject or add a footer break DKIM too. Lists either rewrite From: to their own address ("alice via alumni list") or add an ARC (Authenticated Received Chain) seal that records the checks they saw, which a receiver may choose to trust.
What the page leaves out
Real receivers do much more: STARTTLS and MTA-STS for encryption, SPF records with include:, a, mx and the 10-lookup limit, DKIM canonicalization (c=relaxed/relaxed), several signatures per mail, key rotation by selector, real RSA-2048 and SHA-256, the public suffix list for organizational domains, sp= and pct= tags, forensic reports, BIMI logos, greylisting, IP reputation and content spam filters, and the 4xx retries and bounces of a mail queue. The DNS answers come from one box instead of the resolver chain.