# Fix SPF, DKIM, and DMARC errors on Cloudflare

Diagnose duplicate SPF records, DKIM proxy settings, wrong hostnames, and DMARC alignment with synthetic records and a read-only worksheet.

Author: Stampwing
Canonical: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting
Published: 2026-10-05
Reviewed: 2026-10-05
Category: Deliverability

Match a DNS symptom to a specific record correction and record what still needs live verification.

## Short answer

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#short-answer

Compare your provider’s exact expected record name, type, and value with the authoritative DNS answer. Keep one SPF policy per envelope-sender domain, use DNS-only DKIM CNAMEs, and distinguish published records from authentication and DMARC alignment. A green DNS check does not prove inbox placement.

## Before you start

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#before-you-start

For: Developers building and operating application email.

Bring: Node.js 22+ and npm for the offline fixtures. Optional real lookups use dig and your provider’s expected records.

Scope: The download runs locally with synthetic data. All email delivery is simulated; no provider credentials or live sends are needed.

## Start with the identities in the message

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#identify-domains

Write down the visible From domain, envelope-sender domain, DKIM signing domain, and selector before editing DNS. They can be different. SPF checks the envelope-sender identity; DKIM uses its signing domain and selector; DMARC compares authenticated identities with the visible From domain.

The download deliberately uses one synthetic domain to make record-location mistakes easy to see. A real provider may use a bounce subdomain for the envelope sender and several DKIM selectors. Apply its exact instructions for the selected region, not the simplified fixture values.

Confirm that the domain is actually delegated to the nameservers whose records you are editing. Changing a Cloudflare zone that is not authoritative will not change what receiving servers see.

## Compare eight synthetic DNS configurations

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#run-locally

Extract the ZIP, then run npm ci, npm test, and npm run demo. The eight fixtures cover expected record locations, duplicate SPF, a proxied DKIM CNAME, a repeated zone name, missing records, normalized hostnames and TXT chunks, duplicate DMARC, and a CNAME/TXT conflict. The tests compare each result with explicit expected findings.

No command in the default test or demo queries DNS or edits a zone. The accompanying worksheet has optional read-only dig commands and space for the UTC check time, authoritative and recursive results, expected values, and the next investigation.

Run from the extracted example folder

```sh
npm ci
npm test
npm run demo
```

## Read the key part of the example

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#example-source

This excerpt comes from lab.mjs, lines 46–73, in the download. It shows the central decision; run the complete package with the commands above.

Local simulation · lab.mjs

```javascript
  const dkim = at(`${zone.selector}._domainkey.${zone.domain}`).filter((r) =>
    ["TXT", "CNAME"].includes(r.type),
  );
  if (!dkim.length)
    issues.push("No DKIM record at the expected selector hostname.");
  if (dkim.some((r) => r.type === "CNAME" && r.proxied))
    issues.push("DKIM CNAME is proxied: use DNS only.");
  if (
    zone.records.some((r) => r.name.endsWith(`.${zone.domain}.${zone.domain}`))
  )
    issues.push("A hostname repeats the zone: check the record Name field.");
  const dmarc = at(`_dmarc.${zone.domain}`).filter(
    (r) => r.type === "TXT" && /^v=DMARC1;/i.test(r.value),
  );
  if (!dmarc.length) issues.push("No DMARC record at the expected hostname.");
  if (dmarc.length > 1)
    issues.push(
      "Multiple DMARC records: publish one policy at the expected hostname.",
    );
  for (const name of [...new Set(zone.records.map((record) => record.name))]) {
    const records = at(name),
      aliases = records.filter((record) => record.type === "CNAME");
    if (aliases.length && records.length > 1)
      issues.push(
        `CNAME conflicts with another record at ${name}: review the provider’s required record type.`,
      );
  }
```

## Find the files you’ll change

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#example-files

Pinned dependencies: Node.js built-ins only. Install with npm ci so the lockfile controls the resolved versions.

| File | Purpose |
| --- | --- |
| lab.mjs | The example behavior shown in this article. |
| test.mjs | Acceptance checks and synthetic failure cases. |
| demo.mjs / expected-output.json | A repeatable local experiment and its recorded result. |
| README.md | Setup commands, expected behavior and production boundaries. |
| BUILD-BRIEF.md | The coding-agent brief below. |
| package.json / package-lock.json | Pinned dependencies and runnable commands. |
| LICENSE | MIT license for adapting this example. |

## Compare the recorded local result

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#recorded-experiment

Captured from this package’s demo command on October 5, 2026. These results use synthetic fixtures and simulated delivery; they do not measure a live provider or inbox placement.

Recorded local simulation · expected-output.json

```json
{
  "simulated": true,
  "results": [
    {
      "name": "expected locations",
      "findings": [
        "Fixture has the expected record locations. Authentication and alignment remain unverified."
      ]
    },
    {
      "name": "duplicate SPF",
      "findings": [
        "Multiple SPF records: merge authorized senders into one policy."
      ]
    },
    {
      "name": "proxied DKIM",
      "findings": [
        "DKIM CNAME is proxied: use DNS only."
      ]
    },
    {
      "name": "doubled hostname",
      "findings": [
        "No DKIM record at the expected selector hostname.",
        "A hostname repeats the zone: check the record Name field."
      ]
    },
    {
      "name": "missing records",
      "findings": [
        "No SPF record at the expected envelope-sender domain.",
        "No DKIM record at the expected selector hostname.",
        "No DMARC record at the expected hostname."
      ]
    },
    {
      "name": "case, trailing dot and TXT chunks",
      "findings": [
        "Fixture has the expected record locations. Authentication and alignment remain unverified."
      ]
    },
    {
      "name": "duplicate DMARC",
      "findings": [
        "Multiple DMARC records: publish one policy at the expected hostname."
      ]
    },
    {
      "name": "CNAME and TXT collision",
      "findings": [
        "CNAME conflicts with another record at s1._domainkey.example.test: review the provider’s required record type."
      ]
    }
  ]
}
```

## Fix the record that is wrong

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#record-mistakes

| Evidence | Check | Action |
| --- | --- | --- |
| Two TXT records starting v=spf1 | Both policies apply at the same envelope domain. | Merge legitimate senders into one reviewed policy. |
| DKIM CNAME has proxy enabled | The provider expects DNS delegation to its target. | Use DNS only for that CNAME. |
| selector._domainkey.example.com.example.com | The Name field repeats the zone. | Correct the hostname and recheck authoritative DNS. |
| No _dmarc record at the expected name | The record may have been added at the wrong host. | Compare the exact fully qualified name. |

## Separate authoritative state from cached state

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#propagation

First inspect the authoritative nameserver. Then inspect the recursive resolver used by the affected path. An old recursive answer can remain until its cache lifetime ends; a cached negative answer can also outlive the addition of a previously missing record.

Record both answers and their times rather than repeatedly deleting and recreating records. Lowering a TTL after an answer was cached does not retroactively shorten that cached copy’s lifetime. A provider verification job may have its own polling schedule as well.

TXT records do not use Cloudflare’s HTTP proxy. The proxy setting matters for records such as a DKIM CNAME, where the provider needs the DNS delegation to remain visible. Do not treat every Cloudflare DNS issue as a proxy issue.

## A correct-looking record is only one part of authentication

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#alignment

The fixture checker does not expand SPF includes, count DNS-dependent SPF mechanisms, validate DKIM keys, verify a signature, or calculate DMARC alignment. Its clean result explicitly says that authentication and alignment remain unverified.

Inspect Authentication-Results added by a receiving system you trust. An SPF pass for an unrelated domain and a DKIM pass for another unrelated domain do not establish DMARC alignment with your From address. Use the existing authentication guide for identity examples and the header analyzer to inspect reported results.

[Read reported authentication results](https://www.stampwing.com/tools/email-header-analyzer)

[Understand SPF, DKIM, and DMARC alignment](https://www.stampwing.com/guides/spf-dkim-dmarc-for-developers)

## Make a controlled DNS correction

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#production-setup

Save the current record set, identify every legitimate sender affected by a change, and compare the exact provider requirements. Update only the record whose mismatch you have established. Never publish the fixture’s placeholder SPF include or DKIM target.

After the correction, recheck authoritative DNS and the provider’s verification state. Assess authentication from fresh message evidence separately. Do not jump to an enforcing DMARC policy before evaluating legitimate mail streams and the effects on forwarded messages.

If the current incident is an already quarantined message, fixing DNS will not retrieve it. Keep the mailbox investigation moving while you correct the next-send configuration.

## Does “DNS only” mean the message will pass DKIM?

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#common-question

No. It makes the CNAME available as the provider expects. The selector, signing domain, public key and message signature still need to match. Check the provider’s verification state and Authentication-Results from a controlled delivery when you set up production sending.

## Use with your coding agent

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#coding-agent-brief

Download the example, then copy this brief into your coding agent. The same brief is included as BUILD-BRIEF.md.

Coding-agent build brief

```markdown
# Fix SPF, DKIM, and DMARC errors on Cloudflare — coding-agent brief

## Objective

Explain synthetic SPF, DKIM and DMARC record-location problems without changing real DNS or claiming authentication success.

## Read first

Read README.md, package.json, lab.mjs, test.mjs and demo.mjs. Keep dependency versions pinned to package-lock.json. Use Node.js 22+.

## Dependencies

Node.js built-ins only. Install with npm ci and preserve the lockfile.

## Work

Start at `diagnose` in lab.mjs. Run the existing synthetic fixtures before changing behavior. Preserve the guide's original scenario and add a regression check for each changed failure case.

## Constraints

All email is simulated. Do not add provider credentials, send email, deploy services, or use the application's database. Preserve unknown outcomes instead of claiming delivery. Do not turn the local demonstration into production authentication or a public mail relay. Explain the production setup separately.

## Verification

Run `npm ci`, `npm test`, and `npm run demo`.

## Acceptance

Every fixture must match its expected explanation, including TXT chunks, hostname casing, duplicate policies and CNAME collisions. Reject malformed fixtures. Preserve the warning that record shape cannot prove SPF evaluation or DMARC alignment.
No real tokens, signing secrets, or recipient data appear in logs. All fixtures use synthetic values.
```

## Check the download

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#download-integrity

The ZIP contains 7,678 bytes. Compare its SHA-256 digest with this value before extracting. Downloads are free and require no signup.

SHA-256 · cloudflare-email-dns-troubleshooting.zip

```text
3e993e4e90f757eb2c5749d0072db56d2e441d8e8734e6208e700a9b5bf420ea
```

## Sources

Section link: https://www.stampwing.com/guides/cloudflare-email-dns-troubleshooting#sources

- [Cloudflare: DNS record types](https://developers.cloudflare.com/dns/manage-dns-records/reference/dns-record-types/)
- [Cloudflare: proxy status](https://developers.cloudflare.com/dns/proxy-status/)
- [RFC 7208: SPF record selection](https://www.rfc-editor.org/rfc/rfc7208.html#section-4.5)
- [RFC 7489: DMARC identifier alignment](https://www.rfc-editor.org/rfc/rfc7489.html#section-3.1)

Example download: [Download the local example](https://www.stampwing.com/downloads/cloudflare-email-dns-troubleshooting.zip)

## Related guides

- https://www.stampwing.com/guides/spf-dkim-dmarc-for-developers
- https://www.stampwing.com/guides/email-delivered-but-not-received
- https://www.stampwing.com/guides/migrate-email-provider

Editorial policy: https://www.stampwing.com/resources/editorial
