Who this is for. Whoever owns the DNS and the mail platform for a small or mid-sized organisation: an IT administrator, an MSP engineer, or an owner who has been told “we need DMARC” and wants to know what that will touch. You should be able to edit DNS records. You do not need to be a mail specialist.
SPF, DKIM and DMARC are three separate mechanisms that answer three separate questions, and most of the pain people have with them comes from treating them as one switch. This guide explains what each one actually checks, shows the records and the arithmetic, and then gives a rollout order that avoids the usual self-inflicted outage: enforcing a policy before every legitimate sender has been accounted for.
Everything here is explained from the standards and from how receiving systems generally behave. Nothing on this site can read your DNS, so every record shown uses the reserved name example.com. Read your own records with your own tools before you change anything.
Two sender identities, and why that matters
An email carries two different “from” addresses, and the three mechanisms look at different ones.
- The envelope sender (also called MAIL FROM, or Return-Path once delivered) is given to the receiving server during the SMTP conversation. Bounces go here. Recipients almost never see it.
- The header From is the address in the message that the recipient’s mail program displays. This is the one an attacker needs to forge to be convincing.
SPF checks the envelope sender. DKIM checks a signature that names its own signing domain. Neither of them, on its own, says anything about the header From. That gap is exactly what DMARC closes.
| Mechanism | What it checks | Identity involved | What it cannot tell you |
|---|---|---|---|
| SPF | Was this connecting server allowed to send for the envelope domain? | Envelope sender (and the HELO name when the envelope sender is empty) | Anything about the visible From address |
| DKIM | Does the signature verify, so the signed parts were not altered after signing? | The d= domain in the signature | Whether the signing domain is the one the user sees |
| DMARC | Did SPF or DKIM pass and match the visible From domain? What should the receiver do if not? | Header From | Whether the message is trustworthy; it only speaks to domain identity |
A message can pass SPF and DKIM and still be a fraud, because the attacker used their own domain for both. DMARC is what notices that those domains are not the one in the From line.
Reading the evidence: an Authentication-Results header
Receiving systems record their verdicts in a header. The exact layout varies by receiver, but the shape is consistent. This is a made-up example for illustration:
Authentication-Results: mx.receiver.example;
spf=pass smtp.mailfrom=bounce.vendor-mail.example;
dkim=pass header.d=vendor-mail.example header.s=s2;
dmarc=fail (p=reject) header.from=example.com
Read it line by line. SPF passed, but for bounce.vendor-mail.example, a domain belonging to the sending service. DKIM passed, but the signature was made by vendor-mail.example. Both passes concern a domain that is not example.com, the visible From. So DMARC fails: neither result is aligned with the From domain, and the receiver applies the published policy. If example.com published p=reject, this message is refused.
This one example is also the most common real-world misconfiguration. A third-party service sends mail “as” your domain, passes its own SPF and DKIM, and still fails your DMARC because you never gave it a DKIM key signed under your domain or aligned its bounce domain with yours. Keep this picture in mind through the rest of the guide.
SPF: who may send
An SPF record is a TXT record at the domain name itself. It is a list of rules that the receiving server walks through, in order, until one matches.
example.com. IN TXT "v=spf1 ip4:203.0.113.25 include:spf.protection.outlook.com -all"
Mechanisms and qualifiers
| Term | Meaning | Costs a DNS lookup? |
|---|---|---|
ip4: / ip6: | Match a literal address or range | No |
a / a:name | Match the address records of a name | Yes |
mx | Match the addresses of the domain’s mail exchangers | Yes |
include:name | Evaluate another domain’s SPF record; match if it passes | Yes, plus whatever that record costs |
exists:name | Match if the name resolves | Yes |
ptr | Reverse-DNS match; discouraged by the specification | Yes |
redirect=name | Use another domain’s record in place of this one | Yes |
all | Matches everything; goes last | No |
Each mechanism can carry a qualifier: + (pass, the default), - (fail), ~ (softfail) or ? (neutral). So -all means “anything not matched above fails”, and ~all means “anything not matched above is softfail”.
The 10-lookup limit
The SPF specification (RFC 7208) caps the DNS-querying terms evaluated for a single check at 10. The terms that count are include, a, mx, ptr, exists and redirect. ip4, ip6 and all do not. Exceed the limit and the result is a permanent error, which receivers treat as a failure. The specification also advises implementations to limit “void” lookups, names that resolve to nothing, to two.
The count is recursive, and that is where people get caught. Here is a worked example using invented providers:
example.com. TXT "v=spf1 mx include:_spf.crm.example include:_spf.mail-a.example a:relay.example.com ~all"
_spf.crm.example. TXT "v=spf1 include:_s1.crm.example include:_s2.crm.example -all"
_spf.mail-a.example. TXT "v=spf1 a mx include:_nets.mail-a.example -all"
Count it:
| Term | Lookups |
|---|---|
mx in your record | 1 |
include:_spf.crm.example | 1, then its two inner includes add 2 more |
include:_spf.mail-a.example | 1, then its a and mx and inner include add 3 more |
a:relay.example.com | 1 |
| Total | 9 |
Nine, from three apparently small additions. Add one more marketing platform with a two-level include and you are over the limit. Also note that an mx mechanism can itself trigger further address lookups for each mail exchanger it finds, and the specification limits that too, so a domain with many MX hosts spends its budget faster than the arithmetic above suggests.
Staying under the limit
- Remove senders you no longer use. This is the most effective fix and the one most often skipped.
- Prefer
ip4/ip6for servers you control; they cost nothing. - Send different kinds of mail from different subdomains, each with its own short record. Marketing mail from
news.example.comdoes not need to share a budget with staff mailboxes. - “Flattening” replaces includes with the IP addresses they currently resolve to. It works, but the addresses change without notice, so a flattened record that is not regenerated automatically will silently rot. If you flatten, use a process that re-checks and republishes, not a one-time copy.
One record only
There must be exactly one SPF record at a name. Two TXT records that both begin v=spf1 produce a permanent error, and SPF fails for the whole domain. This usually happens when someone adds a new vendor’s recommended record without merging it into the existing one. Merge them: one v=spf1, with every include: and address in it, and a single all at the end.
~all or -all?
-all says unlisted senders must fail; ~all says they are suspicious. In practice the difference matters less than it used to. Once DMARC is enforcing, the receiver’s decision rests on DMARC, and ~all leaves room for a legitimate sender you forgot while you correct it. A sensible path is ~all while you are still discovering senders and -all once DMARC reports show nothing legitimate failing. Never +all, and avoid ?all: neither makes a useful statement.
What SPF cannot survive
SPF checks the connecting server’s address. When a message is forwarded, the forwarding server connects to the final destination, and its address is not in the original sender’s record. SPF therefore fails for forwarded mail unless the forwarder rewrites the envelope sender. This is not a configuration error on your side. It is the reason DKIM exists alongside SPF, and the reason DMARC passes when either one is aligned.
DKIM: was it altered, and who signed it
DKIM works with a key pair. The sending system holds a private key and adds a DKIM-Signature header covering selected headers and the body. The matching public key is published in your DNS, and the receiver fetches it to verify.
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
d=example.com; s=selector1;
h=from:to:subject:date:message-id;
bh=base64-hash-of-the-body; b=base64-signature
Two fields choose where the key lives: d= is the signing domain and s= is the selector. The receiver looks up the TXT record at <selector>._domainkey.<domain>, here selector1._domainkey.example.com.
Selectors let each sender have its own key
Selectors are why a dozen services can sign for the same domain without sharing a secret. Your mail platform signs with one selector, your invoicing system with another, your marketing platform with a third. Each is published separately and each can be rotated or revoked on its own. Record who owns which selector; a selector nobody can attribute is a risk you cannot reason about.
Some platforms ask you to publish a CNAME for the selector instead of a TXT record. The CNAME points to a name the provider controls, so the provider can rotate the key without you touching DNS. Both approaches are legitimate. Check which one your platform expects.
The public key record, and the 2048-bit problem
selector1._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
The p= value is the base64-encoded public key. A 1024-bit RSA public key encodes to roughly 216 characters and fits comfortably in one DNS string. A 2048-bit key encodes to roughly 392 characters. A single string inside a TXT record can hold at most 255 characters, so the 2048-bit record must be split into several quoted strings inside one record, which resolvers join without adding anything:
selector1._domainkey.example.com. IN TXT (
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyIDAQAB"
)
The placeholders above are not a real key; use the value your sending platform generates. Many DNS hosts split long values for you, and some reject a value over 255 characters until you split it. If a provider’s key will not save, this is the first thing to check. If the key was pasted with stray spaces or line breaks inside the base64, verification fails, so compare it character for character with what the platform gave you.
Key length and rotation
The DKIM updates in RFC 8301 require signers to use RSA keys of at least 1024 bits and recommend at least 2048. Choose 2048 where the sender supports it. Rotate keys on a schedule you can actually keep, and always rotate without a gap:
- Generate and publish a new key under a new selector. Do not overwrite the old one.
- Switch the sending platform to sign with the new selector.
- Leave the old selector published for a period afterwards, because mail signed with it may still be in queues, in transit, or being re-verified.
- Remove the old record once you are confident nothing signed with it is still arriving.
A published record with an empty p= means the key has been revoked, which is the clean way to retire a selector deliberately.
DKIM and alignment
A valid DKIM signature from vendor-mail.example proves the message was not altered. It does not help your DMARC. For DMARC to count the signature, the d= domain has to match the From domain. When you onboard a third-party sender, the step that matters is the one where you publish their DKIM key under your domain (for example s2._domainkey.example.com) so that they sign with d=example.com.
What to do next
This guide covers how a receiving server checks the sending identity, SPF and DKIM. The policy layer that sits on top of them, DMARC alignment, forwarding, the staged rollout and the ordered checklist, is in DMARC alignment and a rollout that does not break mail. Then read Microsoft 365 security for the tenant-side settings that sit beside these DNS records.