DKIM, SPF, and DMARC explained for developers
A developer-focused guide to email authentication protocols. Learn what DKIM, SPF, and DMARC do, why they matter, and how to configure them correctly for your sending domain.
When your application sends an email, the receiving mail server needs to decide whether to trust it. Is this message really from your domain, or is someone impersonating you? Three DNS-based protocols answer that question: SPF, DKIM, and DMARC.
If you have ever seen your transactional emails land in spam despite having valid content, the cause is almost always missing or misconfigured authentication records. This guide explains what each protocol does, how they work together, and how to set them up correctly.
Why email authentication matters
Mailbox providers like Gmail, Outlook, and Yahoo use authentication as a primary signal when deciding where to place your email. Starting in 2024, Gmail and Yahoo began enforcing strict requirements: bulk senders must have valid SPF, DKIM, and DMARC records or their messages will be rejected.
Even if you send low volumes of transactional email, authentication directly affects:
- Inbox placement: Unauthenticated emails are far more likely to land in spam.
- Sender reputation: Failed authentication attempts accumulate negative signals against your domain.
- Protection against spoofing: Without authentication, anyone can send email that appears to come from your domain.
SPF: who is allowed to send
SPF (Sender Policy Framework) is the simplest of the three protocols. The idea: you publish a DNS record on your sending domain that lists every IP address authorized to send email on your behalf. When a receiving mail server gets a message claiming to come from your domain, it looks up that record and checks the sender's IP against the list. If the IP is not listed, the email is considered spoofed and is typically marked as spam or rejected outright.
How SPF works
In practice, SPF verification follows four steps:
- You publish a TXT record in your domain's DNS that lists the IP addresses and services authorized to send email for your domain. For example:
v=spf1 include:amazonses.com ~all - When a receiving server gets an email claiming to be from your domain, it queries DNS for your SPF record.
- It checks whether the sending server's IP address matches one of the authorized mechanisms in the record.
- If a match is found, SPF passes. If no mechanism matches, the
allqualifier at the end of the record determines the outcome (reject, soft fail, or neutral).
SPF record anatomy
An SPF record is a single DNS TXT record. Every token has a specific role.
v=spf1 ip4:203.0.113.0/24 include:amazonses.com -all
Version tag: v=spf1 is required as the first token. It identifies the record as SPF version 1. Without it, the TXT record is ignored during SPF evaluation.
Mechanisms are evaluated left to right. The first match determines the result.
| Mechanism | What it checks | Example |
|---|---|---|
ip4 | Sender IP is in the IPv4 range | ip4:203.0.113.0/24 |
ip6 | Sender IP is in the IPv6 range | ip6:2001:db8::/32 |
include | Recursively check another domain's SPF record | include:amazonses.com |
a | Sender IP matches the domain's A/AAAA record | a:mail.acme.com |
mx | Sender IP matches one of the domain's MX host IPs | mx |
exists | Pass if a DNS A lookup on the target resolves | exists:%{i}.spf.acme.com |
Qualifiers are optional prefixes before a mechanism. They determine the SPF result when that mechanism matches.
| Qualifier | Meaning | SPF result |
|---|---|---|
+ (default) | Allow | pass |
- | Reject | fail |
~ | Accept but mark | softfail |
? | No opinion | neutral |
If you write include:amazonses.com without a prefix, the implicit qualifier is + (pass).
The all mechanism matches every sender IP. It acts as the default rule at the end of the record.
-all: strict. Reject anything not explicitly authorized. Use this once all legitimate senders are listed.~all: permissive. Accept but flag as suspicious. Useful during initial setup while you verify coverage.+all: open. Authorizes every server on the internet to send as your domain. Never use this.?all: neutral. Equivalent to no SPF record for unmatched senders.
Most production domains should use -all.
SPF evaluation in practice
SMTP connection
MAIL FROM: hello@acme.com
DNS lookup
dig TXT acme.com
Parse SPF record
v=spf1 ... -all
Evaluate mechanisms
Left to right, first match wins
SPF pass
include:amazonses.com matched
A receiving server gets an email with this SMTP envelope:
- Sending IP:
54.240.27.42 - MAIL FROM:
hello@acme.com
The server queries DNS for the SPF record:
dig +short TXT acme.com | grep "v=spf1"
Returns:
"v=spf1 ip4:203.0.113.0/24 include:amazonses.com -all"
Evaluation proceeds left to right. The first match wins:
ip4:203.0.113.0/24: Is54.240.27.42in the203.0.113.0/24range (203.0.113.0 to 203.0.113.255)? No. Continue.include:amazonses.com: The server recursively queriesdig +short TXT amazonses.com, retrieves its IP ranges, and checks if54.240.27.42is listed. It is. Result: pass.-all: Not evaluated. A match was already found in step 2.
The receiving server records the result in an email header:
Authentication-Results: mx.recipient.com;spf=pass (sender IP is 54.240.27.42) smtp.mailfrom=acme.com
You can inspect this header in any received email to verify SPF is working. In Gmail, click "Show original" on any message.
SPF pitfalls to avoid
DNS lookup limit: SPF evaluation allows a maximum of 10 DNS lookups. Each include directive triggers at least one lookup, and nested includes count toward the total. If you use multiple email services, your SPF record can easily exceed this limit, causing silent failures.
Check your lookup count with:
dig +short TXT yourdomain.com | grep "v=spf1"
Then follow each include and count nested lookups. The total must stay under 10.
Multiple SPF records: You must have exactly one SPF record per domain. If you have two, both are invalid. When adding a new service, merge its include into your existing record rather than creating a second one.
Flattening: If you exceed the lookup limit, you can flatten your SPF record by resolving the includes into explicit IP ranges. However, this requires maintenance because the IPs of your email providers can change. Automated SPF flattening tools can help.
DKIM: was the message altered
Where SPF verifies the sender's identity, DKIM verifies the message itself. The idea: your email provider signs each outgoing email with a private key. The corresponding public key is published in DNS. When a receiving server gets the email, it retrieves the public key and checks the signature. If the signature is valid, the message was not altered in transit. If it fails, the content was modified or the email was forged.
How DKIM works
DKIM verification is a two-phase process. The sending side signs, the receiving side verifies:
- When your email provider sends a message, it generates a SHA-256 hash of selected headers (
From,To,Subject,Date) and the body. - That hash is encrypted with a private key. The result is attached to the email as a
DKIM-Signatureheader. - The receiving server reads the
DKIM-Signatureheader to extract the domain (d=) and selector (s=). - It queries DNS for the public key at
{selector}._domainkey.{domain}. - It decrypts the signature with the public key and compares the hash against the actual message content.
- If they match, DKIM passes. If not, the message was altered in transit or the signature was forged.
DKIM-Signature header anatomy
Every DKIM-signed email carries a DKIM-Signature header. Here is what each field means:
DKIM-Signature: v=1; a=rsa-sha256; d=acme.com; s=nuntly;h=from:to:subject:date:message-id;bh=2jUSOH9NhtVGCQWNr9BrIA==;b=dkiM3gHSK2n4L9a...
| Field | What it contains |
|---|---|
v | DKIM version (always 1). |
a | Signing algorithm. rsa-sha256 is standard. |
d | The signing domain. Must align with the From domain for DMARC. |
s | The selector. Points to the DNS record containing the public key. |
h | List of headers included in the signature. |
bh | Base64-encoded hash of the message body. |
b | Base64-encoded signature of the headers listed in h. |
DKIM public key record
The public key is published as a TXT record under a specific selector subdomain:
nuntly._domainkey.yourdomain.com TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEB..."
| Field | What it contains |
|---|---|
v | DKIM key version (always DKIM1). |
k | Key type. rsa is standard. |
p | Base64-encoded public key. |
The selector (nuntly in this example) identifies which key pair to use. A single domain can have multiple selectors for different services, so each provider's DKIM records coexist without conflict.
When you add a domain in Nuntly, the platform generates your DKIM keys and provides the exact DNS records you need to publish. See domain security setup for the guided workflow.
DKIM verification in practice
Sign message
SHA-256 hash + RSA signature
Attach signature
DKIM-Signature header
DNS lookup
nuntly._domainkey.acme.com
Verify signature
Public key decrypts hash
DKIM pass
Message integrity verified
A receiving server gets an email with this DKIM-Signature header:
DKIM-Signature: v=1; a=rsa-sha256; d=acme.com; s=nuntly;h=from:to:subject:date:message-id;bh=2jUSOH9NhtVGCQWNr9BrIA==;b=dkiM3gHSK2n4L9a...
Verification proceeds:
- The server extracts
d=acme.comands=nuntlyfrom the header. - It queries DNS:
dig +short TXT nuntly._domainkey.acme.comand retrieves the public key. - It recomputes the hash of the headers listed in
h=and the body, then compares it against the signature decrypted with the public key. - The hashes match. Result: pass.
The receiving server records the result:
Authentication-Results: mx.recipient.com;dkim=pass header.d=acme.com header.s=nuntly
DKIM best practices
- Use 2048-bit keys: 1024-bit keys are increasingly considered weak. Most providers now default to 2048-bit, which offers stronger protection.
- Rotate keys periodically: While not required, rotating DKIM keys annually reduces risk if a private key is ever compromised. When rotating, publish the new key first, wait for DNS propagation, then switch the signing configuration.
- Signed headers: Nuntly signs
From,To,Subject,Date,Message-ID,MIME-Version,Content-Type, andReply-Toby default. No configuration needed on your side.
DMARC: the policy layer
SPF and DKIM verify that an email is authentic. But they do not tell the receiving server what to do when verification fails. Should it deliver the email anyway? Send it to spam? Reject it? Without a policy, each receiving server decides on its own, and those decisions vary widely. DMARC fills that gap. It lets you publish a policy that tells receiving servers exactly how to handle unauthenticated emails from your domain. It also sends you reports so you can see who is sending email as your domain and whether authentication is passing.
How DMARC works
DMARC adds two things on top of SPF and DKIM: a policy and an alignment requirement.
- The receiving server checks SPF and DKIM results for the incoming email.
- DMARC requires at least one of SPF or DKIM to pass and align with the
Fromdomain. Passing alone is not enough. - If neither passes with alignment, DMARC applies the policy you defined:
none(do nothing),quarantine(send to spam), orreject(block entirely). - The receiving server sends aggregate reports to the address you specified, showing all authentication results for your domain.
Alignment explained
SPF and DKIM can pass without aligning. Alignment means the domain used by the protocol matches the domain in the From header that the recipient sees.
Concrete example: you send an email where the From header is hello@acme.com.
- SPF alignment: the envelope
MAIL FROMdomain must matchacme.com(or a subdomain in relaxed mode). - DKIM alignment: the
d=field in the DKIM-Signature must matchacme.com(or a subdomain in relaxed mode).
Two alignment modes:
- Relaxed (the default): subdomains are allowed.
mail.acme.comaligns withacme.com. - Strict: exact domain match only.
mail.acme.comdoes not align withacme.com.
This is why SPF and DKIM can both pass individually and DMARC still fails. If the domains do not align with the From header, DMARC treats them as unauthenticated.
DMARC record anatomy
DMARC is a TXT record published at _dmarc.yourdomain.com:
_dmarc.yourdomain.com TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com; pct=100"
| Parameter | What it controls |
|---|---|
v | Version. Always DMARC1. |
p | Policy: none (monitor only), quarantine (send to spam), reject (block entirely). |
rua | Address to receive aggregate reports (XML format, sent daily by receiving servers). |
ruf | Address to receive forensic reports (individual failure details, optional). |
pct | Percentage of messages to apply the policy to. Useful for gradual rollout. |
adkim | DKIM alignment mode: r (relaxed, default) or s (strict). |
aspf | SPF alignment mode: r (relaxed, default) or s (strict). |
DMARC evaluation in practice
A receiving server gets an email with From: hello@acme.com. It has already checked SPF and DKIM:
- SPF: pass, envelope domain
acme.comaligns withFromdomain. SPF alignment: pass. - DKIM: pass,
d=acme.comaligns withFromdomain. DKIM alignment: pass.
DMARC requires at least one to pass with alignment. Both do. Result: DMARC pass.
The server queries the DMARC policy:
dig +short TXT _dmarc.acme.com
Returns "v=DMARC1; p=reject; rua=mailto:dmarc@acme.com". Since DMARC passed, the policy is not applied. The email is delivered normally.
If DMARC had failed, p=reject would instruct the server to block the email entirely.
The receiving server records the full result:
Authentication-Results: mx.recipient.com;spf=pass smtp.mailfrom=acme.com;dkim=pass header.d=acme.com header.s=nuntly;dmarc=pass header.from=acme.com
Recommended rollout strategy
Do not jump straight to p=reject. Follow this progression:
- Start with
p=none: This monitors authentication without affecting delivery. Collect reports for at least two weeks. - Review your reports: Identify all legitimate sources sending email for your domain. Make sure each one has proper SPF and DKIM records.
- Move to
p=quarantine: Unauthenticated messages go to spam. Monitor for false positives. - Advance to
p=reject: Unauthenticated messages are blocked entirely. This is the strongest protection against spoofing.
_dmarc.yourdomain.com TXT "v=DMARC1; p=reject; rua=mailto:dmarc-reports@yourdomain.com; pct=100"
How the three protocols work together
Each protocol covers a different aspect of email authentication:
- SPF checks who sent the message (server identity).
- DKIM checks what was sent (message integrity).
- DMARC enforces a policy when either check fails and verifies alignment between protocols and the visible
Fromdomain.
A properly configured domain has all three. Here is a complete DNS setup:
; SPFyourdomain.com TXT "v=spf1 include:amazonses.com ~all"; DKIMnuntly._domainkey.yourdomain.com TXT "v=DKIM1; k=rsa; p=MIGfMA0G..."; DMARC_dmarc.yourdomain.com TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com"
Verifying your configuration
After publishing your records, verify them using command-line tools:
# Check SPFdig +short TXT yourdomain.com | grep "v=spf1"# Check DKIM (replace nuntly with your selector)dig +short TXT nuntly._domainkey.yourdomain.com# Check DMARCdig +short TXT _dmarc.yourdomain.com
In Nuntly, the domain security page shows real-time verification status for each DNS record. Once all records propagate, your domain is marked as fully authenticated and ready to send.
Next steps
With SPF, DKIM, and DMARC properly configured, your transactional emails have the authentication foundation they need to reach the inbox reliably. Pair this with delivery monitoring to track your ongoing sender reputation and catch issues early.
Ship emails, not infrastructure
Free plan available. No credit card required.
Start sending free