DNS for WordPress email: SPF, DKIM, DMARC, and the alignment problem

Three DNS record types determine whether mail leaving a WordPress site is trusted by receiving servers. SPF lists the servers authorised to send. DKIM attaches a cryptographic signature that proves the message was not altered in transit. DMARC tells receivers what to do when either check fails, and requests aggregate reports of every IP that sent mail claiming to be from the domain. Without all three, mail is more likely to be quarantined or rejected outright. With all three, and with DMARC policy at p=reject, the domain is protected against spoofing.

This is the DNS layer of the WordPress email stack. For the broader setup that surrounds it (picking an external sending service, choosing and configuring a mailer plugin, then publishing the records covered here) see How to set up email on WordPress.

A WordPress site almost always has more than one sender. The transactional provider routing wp_mail() is one sender. Google Workspace, if the admin email lives there, is another. The hosting MTA that PHP mail() falls back to when no SMTP plugin is configured is a third. A contact form plugin that bypasses wp_mail() and calls mail() directly is a fourth. SPF has to authorise every one. DKIM has to sign every one with a key aligned to the From: domain. DMARC has to see alignment pass on every one before enforcement is safe. Setting up SPF for one sender and ignoring the rest is the standard path to a dmarc=fail row in a report and a quarantined message.

SPF: listing every sender

The SPF record for a WordPress site with a transactional provider, a Workspace domain, and a hosting MTA needs to cover all three:

v=spf1 include:_spf.google.com include:sendgrid.net include:_spf.web-hosting-co.com ~all

Each include: mechanism pulls in that provider’s own SPF record. The ~all at the end is a SoftFail: the sending domain is signalling that anything not in the list is probably not authorised, but receivers should not reject solely on that result (RFC 7208 § 2.4). A -all HardFail is an explicit statement that unlisted senders are not authorised; that is the destination once DMARC alignment is verified and stable, but SoftFail is safer during initial setup because a single missed sender will not cause outright rejection.

The 10-lookup limit

RFC 7208 § 4.6.4 imposes a hard cap of 10 DNS lookups per SPF evaluation. Each include: mechanism costs one lookup, and provider SPF records often chain their own include: mechanisms. A site with four providers can easily exceed the limit without knowing it.

When a receiver hits the limit, SPF returns permerror instead of pass or fail. In a DMARC report, permerror shows up alongside a dmarc=fail; the message failed authentication not because the sender was wrong, but because SPF could not finish evaluating the record.

To check the lookup count: paste the SPF record into MXToolbox’s SPF checker, which counts the lookups and flags an over-limit record. Note that dig +short txt example.com shows the raw published record but does not recursively count the lookups inside each include: chain; MXToolbox is the practical tool for that count. The two real fixes are either removing a redundant sender (if, for example, the hosting MTA is never used and exists only as a leftover) or using an SPF flattening service that pre-resolves all the include: chains into their constituent IPs and rewrites the record as a flat IP list. Flattened records need maintenance (provider IP ranges change), so automatic flattening services (AutoSPF, EasyDMARC’s flattener) are more practical than a manual flat list.

The alignment trap

An SPF pass is not the same as SPF alignment. DMARC alignment requires that the domain in the From: header matches the domain against which SPF passed. SPF evaluates against the envelope sender (the Return-Path or MAIL FROM address), which is typically the provider’s bounce domain, not the WordPress site’s domain.

This means SPF alignment is structurally unavailable on most transactional provider configurations. The Return-Path is bounces.sendgrid.net, not example.com. SPF may pass (the sending IP is in SendGrid’s SPF record), but SPF does not align (the authenticated domain is sendgrid.net, not example.com). Editing the SPF record at the domain does not fix this; the Return-Path domain is controlled by the provider, not the operator. DKIM alignment is the practical path: if DKIM signs the message with d=example.com matching the From: header domain, DMARC passes on DKIM alignment regardless of SPF alignment.

DKIM: signing the message

The DKIM signature matters to DMARC only if the d= tag in the signature matches the From: domain. Most providers sign with their own d= by default: the provider’s domain, not the sender’s. The configuration that changes this is usually labelled “domain authentication,” “sender domain authentication,” or “branded links” in the provider’s settings and must be enabled there before DKIM alignment is possible. The DNS records have nothing to authenticate against until the provider-side configuration is in place.

A DKIM signature is a header added to the outgoing message by the sending server. The server signs it with a private key; the receiving server retrieves the matching public key from a DNS record and verifies the signature. If it checks out, the receiver knows the message came from an authorised server and was not modified in transit.

The DNS record is published at selector._domainkey.example.com. The selector is provider-specific; a domain can publish multiple DKIM keys for different sending services simultaneously.

Providers implement DKIM publishing in two patterns:

Direct TXT record. The provider generates a key pair and gives the operator the public key to publish as a TXT record at a specific selector. Postmark uses this pattern: the selector follows the format {timestamp}pm._domainkey (e.g. 2023060112345pm._domainkey.example.com), where the timestamp-based string is provided in the Postmark dashboard. The operator publishes the TXT record, the provider validates it, and DKIM is active.

CNAME pointer. The provider manages the key and publishes it at their own subdomain. The operator publishes a CNAME at the relevant selector that points to the provider’s key. SendGrid uses this pattern: the operator publishes s1._domainkey.example.com CNAME s1.domainkey.[account-id].sendgrid.net and s2._domainkey.example.com CNAME s2.domainkey.[account-id].sendgrid.net. Mailgun’s Automatic Sender Security (the current default) uses pdk1._domainkey and pdk2._domainkey as CNAMEs pointing to Mailgun’s key infrastructure; key rotation is automatic and the operator does not manage it. Brevo provides two CNAMEs: mail._domainkey and mail2._domainkey. The advantage of the CNAME pattern is that providers can rotate keys without the operator touching DNS; the trade-off is that the DKIM record depends on the provider’s own DNS infrastructure remaining authoritative.

Both patterns result in a validly signed message with d=example.com, but only if domain authentication is configured at the provider level. A test send is the only reliable way to confirm the signing domain.

Provider-managed keys use 2048-bit keys by default. The operator publishes what the provider instructs and does not manage key generation or rotation independently.

DMARC: policy and reporting

A DMARC record is a TXT record at _dmarc.example.com. A minimal starting record:

v=DMARC1; p=none; rua=mailto:[email protected]

p=none means monitor without enforcement: messages that fail alignment are not quarantined or rejected, but aggregate reports are sent to the rua= address. This is the correct starting policy: collect a baseline of what receivers see before enforcing anything.

The three policy values:

  • p=none: collect reports, take no enforcement action.
  • p=quarantine: route failing messages to the spam folder.
  • p=reject: refuse delivery of failing messages.

The pct= parameter samples the message stream and applies the policy to that percentage. At pct=25, receivers enforce quarantine or reject on a random 25% of messages subject to DMARC evaluation; the remaining 75% are treated as if the policy were p=none, regardless of whether they pass or fail. At pct=25, a misconfigured sender gets quarantined on a quarter of its messages rather than all of them; the remaining three-quarters still deliver, which limits the damage from a sender the operator forgot to fix.

Alignment. DMARC’s pass condition is that the From: domain aligns with either the SPF-authenticated domain or the DKIM d= domain; one alignment is sufficient. Relaxed alignment (the default, per RFC 7489 § 3.1.1) requires the same organisational domain, so mail.example.com aligns with example.com. Strict alignment requires an exact FQDN match. Most WordPress sites want relaxed.

Subdomain policy. The sp= tag controls the policy for mail claiming to come from a subdomain (e.g. transactional.example.com when DMARC is published only at example.com). Without sp=, receivers default to the organisational-domain policy. During p=none monitoring this is harmless; before moving to p=quarantine, verify that no legitimate mail originates from subdomains that aren’t separately authenticated.

Bulk-sender requirements. Google’s 2024 requirements (effective February 2024) mandate that senders delivering more than 5,000 messages per day to Gmail accounts have DMARC published, at a minimum of p=none. A combined WordPress site (A transactional email is the automated message a WordPress site sends in response to a single user action – a password reset, an order confirmation, a form receipt – addressed to the user who triggered it. Read full reference →, plugin-triggered notifications, contact form submissions, and marketing automation on the same domain) can reach that threshold faster than operators expect.

Reporting. Aggregate reports (rua=) arrive as gzipped XML attachments, one per receiver per day. The XML lists every IP that sent mail claiming to be from the domain, the count of messages, and whether each row passed or failed alignment. Reading raw XML at any volume is impractical; the DMARC aggregate reports guide covers the parsing tools and the diagnostic loop in full.

The alignment problem in production

The most common DMARC failure pattern on WordPress sites is the one that looks like everything is configured correctly: spf=pass, dkim=pass, dmarc=fail.

Both authentication checks passed, but DMARC failed alignment. This happens when:

  • SPF passes against the provider’s bounce domain (bounces.mailgun.org), not the From: domain. SPF passed; SPF did not align.
  • DKIM signed with the provider’s own d= (sendgrid.net) instead of the site’s domain. DKIM passed; DKIM did not align.

Neither alignment check was satisfied, so DMARC fails despite both authentication checks passing.

Diagnosing from a report. The aggregate report row for this pattern looks like:

<row>
  <source_ip>167.89.123.45</source_ip>
  <count>47</count>
  <policy_evaluated>
    <dmarc>fail</dmarc>
    <dkim>pass</dkim>
    <spf>pass</spf>
  </policy_evaluated>
  <auth_results>
    <dkim>
      <domain>sendgrid.net</domain>
      <result>pass</result>
    </dkim>
    <spf>
      <domain>em1234.example.com</domain>
      <result>pass</result>
    </spf>
  </auth_results>
</row>

The auth_results block tells the story: dkim domain: sendgrid.net (the signature is SendGrid’s, not the site’s) and spf domain: em1234.example.com (the envelope sender is a SendGrid subdomain). Neither aligns with the From: domain. Fix: enable domain authentication in SendGrid so DKIM signs with d=example.com.

The contact form alignment failure. Contact forms introduce a second pattern: the operator correctly configures the SMTP relay to sign DKIM with the site’s domain, but the contact form plugin sets the message From: to the visitor’s email address (taken from the form field). The DKIM signature’s d= is example.com; the From: is [email protected]. DKIM alignment fails because the domains don’t match.

The fix is a configuration change in the contact form plugin: set From: to a fixed address on the site’s domain ([email protected] or similar), and put the visitor’s address in Reply-To:. The contact form reply-to guide covers the configuration per plugin.

Undocumented senders. DMARC reports regularly surface sending IPs the operator didn’t know existed. The most common and most surprising: a backup MTA at the host that fires when the SMTP plugin fails (the host’s own mail server, with no DKIM and no SPF alignment) appears in the report with its own row. Behind it: a legacy plugin that was deactivated but not uninstalled and still calls mail() directly; and third-party services (monitoring tools, Zapier, notification relays) that send from the site’s domain without going through the configured relay. Before moving the DMARC policy to enforcement, every IP in the pass rows needs to be identified and kept, and every IP in the fail rows needs to be either fixed or eliminated.

Migrating to enforcement

The migration from p=none to p=reject typically takes four to eight weeks. Low-volume sites (under a few hundred messages per day) need longer monitoring windows to accumulate enough report data to be confident the record set is complete; high-volume sites accumulate that confidence faster.

Week 0. Publish p=none with a rua= address the operator actually monitors. Set the DMARC record TTL to 300 (five minutes) so policy changes propagate quickly during migration.

Weeks 1–3. Review reports daily. Build a complete picture of every sending IP: which provider it belongs to, whether it’s aligned, whether it’s legitimate. Fix alignment failures: enable domain authentication on every transactional provider, fix contact form From: configurations, route or eliminate senders that bypass wp_mail(). Do not advance to enforcement until the report shows zero dmarc=fail rows from legitimate senders and every identified IP is aligned.

Week 4. Move to p=quarantine; pct=25. Run for one week. Watch for support tickets. Check reports for any IP that wasn’t identified in the monitoring phase.

Weeks 5–6. pct=50, then pct=100. Each increase at weekly intervals. At p=quarantine; pct=100, all failing messages from the domain are routed to the spam folder at receivers that honour DMARC policy.

Week 7 onward. p=reject. All failing messages are refused. The reporting remains useful for catching newly introduced alignment failures: a new plugin, a new integration, a provider change that alters the signing configuration.

Rolling back. If a problem appears mid-migration, drop pct= or revert to p=none. The change takes effect within the TTL of the DMARC record.

For diagnosing broader delivery problems once the record set is confirmed (SMTP plugin configuration, host-level blocking, inbox placement), see the WordPress email troubleshooting guide.

Provider-specific setup: records to publish

The records to publish are specific to the sending service. The links below go to each provider’s own documentation for configuring SPF, DKIM, and DMARC.

Hosting providers Email service providers
A2 Hosting<br>Bluehost<br>Cloudways<br>Dreamhost<br>FastComet<br>GreenGeeks<br>Hostinger<br>Inmotion Hosting<br>Kinsta<br>Liquid Web<br>Nexcess<br>Pagely<br>Pantheon<br>Pressable<br>Presslabs<br>Siteground<br>WP Engine<br>WPX Hosting Amazon SES (AWS docs)<br>Brevo (formerly Sendinblue) (Brevo docs)<br>Elastic Email<br>Gmail / Google Workspace<br>MailPoet<br>Mailchimp Transactional (Mandrill)<br>Mailgun (Mailgun docs)<br>Mailhawk<br>Mailjet<br>Bird (formerly MessageBird, formerly SparkPost)<br>Netcore Email API (formerly Pepipost)<br>Postmark (Postmark docs)<br>SMTP.com<br>SMTP2GO (SMTP2GO docs)<br>SendGrid (SendGrid docs)<br>SendLayer<br>Zeptomail (by Zoho)<br>Zoho Mail

Verifying the records

dig is the authoritative check. It queries DNS directly and returns exactly what is published:

# SPF
dig +short txt example.com

# DKIM (replace selector and domain)
dig +short txt 2023060112345pm._domainkey.example.com

# DMARC
dig +short txt _dmarc.example.com

Note that dig shows the raw published record but does not count the DNS lookups recursively inside each include: chain. For SPF lookup-count verification, use MXToolbox rather than trying to count manually.

MXToolbox provides syntax checking and SPF lookup counting. Paste the SPF record and MXToolbox reports how many DNS lookups it requires. Any count over 10 means permerror on evaluation.

Mail-Tester provides an end-to-end test: send a real message to the address Mail-Tester generates and receive a scored report covering SPF, DKIM, and DMARC results plus content scoring. This catches alignment failures that record inspection misses: the DKIM record may be published correctly, but the provider may not be signing with d=example.com. A test send is the only reliable way to confirm the signing domain.

The testing methodology: send a test message from every sender on the domain. The WordPress password reset email, a contact form submission, a test send from the transactional provider’s dashboard. Run each through Mail-Tester and confirm alignment passes on all of them before advancing the DMARC policy beyond p=none.