If you are reading this, you have almost certainly already installed an SMTP plugin and your WordPress mail still lands in spam. The SMTP layer gets the message to a real sending service; the DNS layer tells Gmail, Outlook and Yahoo that the sending service is allowed to speak for your domain. Without the DNS layer, receivers have no way to tell your legitimate mail apart from somebody spoofing your From address. Since February 2024, they stop guessing and start rejecting.
This article walks through what SPF, DKIM and DMARC each do, what the current industry baseline looks like, and how to publish all three records for a WordPress site without locking yourself out of your own inbox in the process.
Why WordPress email lands in spam: the authentication gap
WordPress's default wp_mail() path hands every message to PHP's mail() function, which relays through the local MTA on your web server's IP. That IP is almost never listed in your domain's SPF record, the server does not hold your DKIM private key, and the From header says wordpress@yoursite.nl regardless. Receiving servers apply three tests:
- Is the sending IP allowed to send for
yoursite.nl? (SPF) - Is the message body signed by a key published at
yoursite.nl? (DKIM) - Does the
Fromdomain match one of the two domains that just passed? (DMARC alignment)
All three fail, and the message either lands in junk or is rejected outright. Since Google and Yahoo's bulk sender rules took effect on February 1, 2024, and Microsoft's equivalent enforcement started on May 5, 2025, the practical baseline for every WordPress domain is SPF + DKIM + DMARC, regardless of volume. The bulk-sender thresholds only decide whether enforcement is a spam flag or an outright rejection; the requirement itself applies to everyone.
Before you add the DNS records, the WordPress side needs to be ready: set up a proper SMTP relay through a service that can sign DKIM for your domain. If you have not done that yet, work through WordPress SMTP configuration first. SPF, DKIM and DMARC only help if there is a real sending service to authenticate; they do not fix PHP mail().
What SPF does and how to create the record
SPF (Sender Policy Framework, RFC 7208) is a DNS TXT record that lists which mail servers are allowed to send from your domain's envelope sender. When a receiving mail server gets a message claiming to be from yoursite.nl, it looks up your SPF record, compares the sending IP to the list, and returns pass, fail, softfail, neutral or permerror.
An SPF record looks like this:
v=spf1 include:_spf.google.com include:mailgun.org ip4:203.0.113.0/24 -all
Breaking that down:
v=spf1marks this as SPF version 1. Required.include:_spf.google.comdelegates to Google's SPF record (covers Google Workspace outbound).include:mailgun.orgdelegates to Mailgun's SPF record (covers your transactional sending).ip4:203.0.113.0/24allows a specific range of IPs directly. Pick this when you have a fixed outbound IP, for example a dedicated server.-allis a hard fail. Anything not matched by the mechanisms before it should be rejected. Use-allin production.~all(softfail) is acceptable during initial testing, when you want receivers to accept but flag suspicious messages rather than reject them.
How to publish the record
- In your domain's DNS zone, create a TXT record at the apex (the host name is
@or the blank root, depending on your DNS provider's UI). - Set the value to the SPF string provided by your sending service (Mailgun, SendGrid, Amazon SES and Postmark each show you the exact include during their domain verification wizard).
- Save. DNS propagation typically takes minutes on modern providers; older ones may take up to 24 hours.
Only one SPF record per domain. RFC 7208 explicitly forbids multiple SPF TXT records at the same host. If you publish two, most receivers treat the result as permerror and fail the message. When you add a second sending provider, merge it into the existing record by adding another include: rather than creating a new TXT record.
Verify the record
Paste your domain into MXToolbox SPF lookup and confirm the "SPF Record Valid" and "SPF Syntax Check" results are green. You should see a single SPF record. If MXToolbox reports two SPF records, remove one in your DNS zone and merge the includes into the remaining record.
If you have SSH access: run dig TXT yoursite.nl +short and confirm you see a single line starting with "v=spf1".
SPF alone is not enough and has not been since DMARC was introduced. It validates the envelope sender, which receivers never show in the email client, and it breaks the moment a message is forwarded because the forwarding server changes the envelope. The other two records cover those gaps.
What DKIM does and where the key lives
DKIM (DomainKeys Identified Mail, RFC 6376) cryptographically signs the message body and selected headers with a private key held by your sending service. The signature travels in the DKIM-Signature header of the message itself. Receivers fetch the matching public key from your DNS zone and verify that the signed content has not been modified in transit and that the signing domain vouches for the message.
A DKIM DNS record looks like this:
s1._domainkey.yoursite.nl. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."
The important pieces:
- Selector (
s1in the example): an arbitrary label that lets you publish multiple keys per domain. Your sending service picks the selector name; you just publish what they give you. v=DKIM1: DKIM version. Required.k=rsa: key algorithm.rsais the default and what every major service uses.p=...: the base64-encoded public key. Emptyp=revokes the key.
The WordPress site itself cannot DKIM-sign mail. Only the SMTP relay that actually puts the message on the wire can sign it, because only it holds the private key. That is why DKIM is configured at the sending service (Mailgun, SendGrid, Amazon SES, Postmark, Google Workspace, Microsoft 365), not in WordPress. The service generates the keypair, keeps the private key, and gives you the public key to publish in your DNS zone.
Use a 2048-bit key, not 1024
RFC 6376 sets the floor at 1024 bits, but research has demonstrated that 1024-bit DKIM keys can be factored with cloud compute in under four days, and Gmail publicly prefers 2048-bit keys. Generate 2048-bit RSA keys for any new setup. The only reason to fall back to 1024 is if your DNS provider's control panel cannot store a TXT value longer than 255 characters and cannot split it across multiple quoted strings, which is rare in 2026 but still happens on older registrar UIs.
If you see your sending service offering "1024-bit" and "2048-bit" as options, pick 2048. If it does not offer a choice, check the key length in DNS by entering s1._domainkey.yoursite.nl (replace s1 with your actual selector) in MXToolbox DKIM lookup. The p= value's character count roughly corresponds to key length. A 1024-bit key produces a p= value of around 216 characters; a 2048-bit key produces around 392 characters. If yours is at the low end, check your sending service's dashboard for an option to regenerate at 2048 bits.
If you have SSH access: run dig TXT s1._domainkey.yoursite.nl +short to see the raw record directly.
The managed hosting DKIM trap
Several managed WordPress hosts pre-configure DKIM for their own sending domain, not yours. Kinsta's MailChannels integration, for example, signs outbound mail with d=kinstamailservice.com. The signature passes verification against that domain, but DMARC then checks whether the signing domain aligns with the From header. kinstamailservice.com does not align with yoursite.nl, so DMARC fails even though DKIM itself passed.
The fix: use a transactional email service (Mailgun, SendGrid, Postmark, Amazon SES, Resend) that lets you verify your own sending domain and publishes a DKIM key under yoursite.nl. Then both DKIM verification and DMARC alignment pass. If your managed host offers "custom domain DKIM", use it; otherwise, route mail through a dedicated transactional service.
Verify the record
After publishing, test the full signing chain by sending a real message from WordPress to a Gmail inbox, opening the message, clicking the three-dot menu and picking Show original. The Authentication-Results header should read something like:
Authentication-Results: mx.google.com;
dkim=pass header.i=@yoursite.nl header.s=s1
dkim=pass and header.i=@yoursite.nl are what you are looking for. If header.i shows a different domain, the signature is aligned against something other than your From domain and DMARC will fail.
What DMARC does and how to set it up in stages
DMARC (Domain-based Message Authentication, Reporting and Conformance, RFC 7489) does two things: it tells receivers what to do with messages that fail authentication, and it asks them to send back aggregate reports so you can see who is sending mail in your name.
The core concept DMARC adds on top of SPF and DKIM is alignment. A passing SPF or DKIM check is not enough; the domain that passed must also match the From header domain the user sees. Without alignment, a spammer can buy cheap-domain.com, publish a valid SPF record for it, and send mail with From: you@yoursite.nl that passes SPF. DMARC is what catches that.
A DMARC DNS record looks like this:
_dmarc.yoursite.nl. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc@yoursite.nl; fo=1"
The important tags:
v=DMARC1: version. Required.p=none|p=quarantine|p=reject: the enforcement policy.nonemeans "monitor only",quarantinemeans "route failures to spam",rejectmeans "bounce failures at the server". Start atnoneand work up.rua=mailto:dmarc@yoursite.nl: where daily aggregate reports should be sent.fo=1: ask for a forensic report if either SPF or DKIM fails (not just if both fail). Mostly symbolic now; see the next section.sp=reject: optional, applies the policy to subdomains separately. Without it, subdomains inheritp.pct=10: optional, applies the policy to a percentage of failing messages. Useful during rollout.
The staged rollout: none → quarantine → reject
Do not publish p=reject on day one. Immediate p=reject is the single fastest way to break legitimate WordPress mail for weeks. Every forgotten sending source (backup notifications, uptime monitors, newsletter platforms, the WooCommerce order queue on a second plugin, the CRM you set up two years ago) will have its mail rejected without retry, without logging on the recipient side, without any visibility for you. I have seen small shops miss two weeks of customer invoices this way.
The correct rollout sequence from dmarcian is:
- Week 0: publish
p=none; rua=mailto:dmarc@yoursite.nl. No enforcement. Just collect data. - Weeks 1 to 4: read the aggregate reports. Every sending source that is legitimate should show up as passing both SPF alignment and DKIM alignment. Fix any that fail. For a busy WooCommerce store, give this four weeks; for a small brochure site with one contact form, two weeks is enough.
- Week 4 or 5: move to
p=quarantine; pct=10. Now 10 percent of failing messages go to the recipient's spam folder. Keep watching reports for legitimate sources that suddenly start failing. - Week 6: raise to
p=quarantine; pct=25, thenpct=50, thenpct=100over the following one to two weeks. - Week 8 to 10: move to
p=reject; pct=10, thenpct=25,pct=50,pct=100.
For a typical small WordPress site with one or two sending sources, budget four to eight weeks. For a complex site with multiple integrated services (CRM, email marketing, transactional, internal notifications), budget three to six months. Rushing this is the single most common way to break your own mail.
Reading DMARC aggregate reports, and why forensic reports are dead
DMARC reports come in two flavors. Only one of them is still useful.
Aggregate reports (rua) are daily XML files that every major receiver sends back to the address in your rua= tag. Each report lists the sending IPs that tried to send in your name, the volume per source, the SPF and DKIM results, the alignment results and the DMARC disposition (none, quarantine or reject). They never contain message subjects, bodies or recipient addresses, which is why they survived the privacy-regulation wave.
Raw XML is painful to read. Pipe your reports into a DMARC analyzer instead. Free tiers at dmarcian, EasyDMARC and MXToolbox DMARC reports give you a dashboard view of sending sources, alignment rates and trends. Point your rua= tag at the address the analyzer gives you (or at your own inbox, then forward).
Forensic reports (ruf), in theory, give you a redacted copy of individual failing messages in real time. In practice, they are largely dead. Gmail does not send ruf reports. Yahoo does not send them either. Most large providers stopped between 2020 and 2022 because of GDPR and related privacy concerns. You can keep fo=1 in your DMARC record for the smaller receivers that still send forensic reports, but do not rely on ruf for visibility. Aggregate reports are your diagnostic tool.
What to do with aggregate reports in week 1:
- Open your analyzer dashboard.
- Look at the "sending sources" list. Every legitimate sender should appear there.
- For each source, confirm both DKIM alignment and/or SPF alignment show as passing. Either one is enough for DMARC to pass (DKIM is preferred because it survives forwarding).
- If a source shows up that you do not recognize, that is either a forgotten legitimate service or spoofing. Investigate before raising policy.
- If a legitimate source shows as failing, fix it before raising policy, or you will quarantine your own mail.
Testing your records end to end
Before and after each policy change, run two end-to-end checks.
Check 1: MXToolbox SuperTool. Point it at your domain and run the SPF, DKIM and DMARC lookups. Expected output: three green "record found" results with no syntax errors. MXToolbox also flags SPF 10-lookup problems, which a raw DNS query will not.
Check 2: mail-tester.com. Open mail-tester.com, copy the unique address it shows you, and send a test email from WordPress to that address (a password reset, a contact form submission, or any real wp_mail() call). Then click "Then check your score". A good result is 10 out of 10 with green checkmarks on SPF, DKIM and DMARC. Anything red tells you exactly which record is wrong.
Combine the two: MXToolbox confirms the records are syntactically valid; mail-tester confirms they actually authenticate a real WordPress message end to end. I run both every time I change DNS or add a sending source.
Common mistakes that quietly break everything
Almost every "my DKIM passes but DMARC still fails" ticket comes down to one of these.
Multiple SPF records on the same domain
RFC 7208 forbids this, and receivers treat it as permerror. It happens when someone adds a second include by creating a new TXT record instead of editing the existing one, or when a hosting control panel auto-publishes an SPF record that nobody notices alongside the one you published manually. Check by pasting your domain into MXToolbox SPF lookup. If it reports multiple SPF records, merge the includes into one record in your hosting panel's DNS zone editor and delete the other.
Exceeding the SPF 10-lookup limit
RFC 7208 section 4.6.4 allows at most 10 DNS lookups per SPF evaluation. Mechanisms that count toward the limit: include, a, mx, ptr, exists, and the redirect modifier. Mechanisms that do not count: ip4, ip6, all.
The trap is that include: is transitive. include:_spf.google.com by itself is one lookup, but Google's SPF record contains more includes, each of which also counts. A WordPress site that uses Google Workspace for staff mail, SendGrid for transactional, Mailchimp for newsletters and a SaaS CRM that sends from yet another service quickly lands at 11 or 12 lookups and turns into permerror. DMARC then treats the whole thing as an SPF fail.
Fixes, in order of preference:
- Consolidate sending. Do you actually need all four services? Pick the smallest set.
- Replace includes with
ip4:for services that publish a stable IP range. - Rely on DKIM alignment for DMARC, so the occasional SPF fail does not cascade into DMARC failure.
- SPF flattening services (Valimail's explainer covers the caveats): they replace your includes with the current IP list. Cheap and effective until the provider's IPs change and your flattened record goes stale. Only use flattening if you can monitor it.
Void lookups tipping the record over
SPF also enforces a softer limit: no more than two "void lookups" (DNS queries that return NXDOMAIN or an empty response) per evaluation. Typos in include targets and includes pointing at decommissioned domains both trigger void lookups. Exceeding two also results in permerror. Audit your includes every time you change sending providers.
Wrong DKIM selector
Every sending service uses its own selector naming scheme: Mailgun uses k1._domainkey, SendGrid uses s1._domainkey and s2._domainkey, Google Workspace uses google._domainkey. Copying the selector wrong (or publishing under _domainkey with no selector, which some registrar UIs do silently) produces a DKIM lookup that returns nothing. The message is then either unsigned or signed with a selector nobody can find. Verify by entering the selector and domain in MXToolbox DKIM lookup using the exact selector your service documented. If you have SSH access: dig TXT selector._domainkey.yoursite.nl +short gives you the same answer from the command line.
Setting p=reject before fixing forgotten sources
Covered above under the staged rollout, but it deserves repeating: do not set p=reject on day one, and do not skip the p=quarantine stage. Every time I have seen a WordPress site break its own mail with DMARC, it was because someone published p=reject without reading their aggregate reports first.
WooCommerce and other high-volume WordPress senders
WooCommerce sends order confirmations, payment receipts, refund notifications, shipping updates and customer invoices, all through wp_mail(). That means they all inherit whatever SMTP path and DKIM setup you configured at the WordPress level. There is no separate WooCommerce authentication layer to configure.
What WooCommerce does add is volume. On a busy store you may cross the 5,000-messages-per-day threshold that Google and Microsoft use to classify domains as "bulk senders". Once classified, the classification is permanent regardless of future volume. Bulk sender requirements go beyond the minimum baseline:
- Spam complaint rate must stay below 0.10 percent (warning threshold) and strictly below 0.30 percent (hard limit with enforcement).
- One-click unsubscribe (RFC 8058
List-Unsubscribe-Post) for marketing and promotional messages. - Valid forward and reverse DNS on the sending IPs.
- TLS on message transmission.
- Functional From and Reply-To addresses (a
noreply@that bounces hard is discouraged).
For most WooCommerce stores, the practical consequence is: use a dedicated transactional email service (Postmark, Mailgun, Amazon SES) for order mail, use a separate sending domain or subdomain for marketing mail sent through Mailchimp or similar, and keep DMARC aligned on both. If you ever need to send the same domain's traffic through two services, a common pattern is to send transactional mail from yoursite.nl and marketing mail from mail.yoursite.nl, each with its own aligned authentication chain. The WooCommerce email transport documentation confirms that WooCommerce itself is transport-agnostic: every email goes through wp_mail(), which means every email inherits the WordPress-level SMTP and DKIM setup.
When to escalate
If you have worked through this article and DMARC still fails intermittently, collect the following before asking your host or sending service for help:
- Screenshots or text copies of your SPF, DKIM and DMARC records as currently published. The quickest way: run all three lookups in MXToolbox SuperTool and copy the results. If you have SSH access,
dig TXT yoursite.nl +short,dig TXT selector._domainkey.yoursite.nl +shortanddig TXT _dmarc.yoursite.nl +shortgive the same data. - A recent DMARC aggregate report showing the failing source and the alignment result.
- The full
Authentication-Resultsheader from a failing message, copied from Gmail's "Show original" view. - Your sending service name, plan and the exact sending domain you verified.
- Your WordPress SMTP plugin name and version.
- Whether the failing messages are transactional (
wp_mail()triggered by WordPress) or marketing (sent through a separate platform like Mailchimp).
With that list, a support engineer on the sending service or a DNS-savvy colleague can usually pinpoint the problem in minutes. Without it, expect a multi-day back-and-forth. For the broader "WordPress is not sending mail at all" case, the layer-by-layer diagnosis in why WordPress is not sending email covers the WordPress, hosting and DNS layers together and is the right place to start if you are not sure which layer is broken.