Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Set Up DMARC for Outbound Email (and Check It Works)

Four DNS queries, one record, and a bounce check. About 30 minutes, then a few weeks of watching reports.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

DMARC tells receiving servers what to do when a message claiming to be from your domain fails SPF and DKIM. For outbound, you publish one TXT record, read the reports, then tighten the policy. This guide shows the queries and the output, not the theory.

TL;DR

  • Publish a p=none DMARC record on the domain you send from, with a reporting address. It takes about 30 minutes.
  • Read aggregate reports for 2 to 4 weeks, fix the senders that fail, then move to p=quarantine and p=reject.
  • Pair it with a clean list. A verified-only LeadOcean pull of 25 contacts spends 25 records. The counting call spends none.

Prerequisites

  • Access to DNS for your sending domain (Cloudflare, Route 53, your registrar).
  • SPF and DKIM already published and passing. DMARC passes when either one passes and aligns with the From domain (RFC 7489, September 2026).
  • A mailbox that can receive XML reports, or a free report parser.
  • dig on your machine.
  • Optional: a LeadOcean key as $LEADOCEAN_API_KEY for the list-hygiene step. Get one free at app.leadocean.io.

Step 1: Check what your domain publishes today

Query the _dmarc label. Replace acme.com with your sending domain.

bash
dig +short TXT _dmarc.acme.com
dig +short TXT acme.com | grep spf
dig +short TXT selector1._domainkey.acme.com

An empty answer on the first line means no DMARC record. Gmail requires SPF, DKIM and DMARC from bulk senders (5,000 or more messages a day). It may reject unauthenticated mail with a 5.7.26 error (Google, October 2026). Smaller cold senders should publish it anyway.

Step 2: Publish a monitoring record

Add one TXT record at _dmarc.acme.com. Start with p=none: it blocks nothing and starts the reports.

text
Host:   _dmarc
Type:   TXT
Value:  v=DMARC1; p=none; rua=mailto:dmarc-reports@acme.com; adkim=r; aspf=r; pct=100

What each tag does:

TagMeaning
pPolicy for failing mail: none, quarantine or reject
ruaWhere aggregate (daily XML) reports go
adkim, aspfr relaxed or s strict alignment
pctShare of failing mail the policy applies to
spPolicy for subdomains, if different

Confirm it published:

bash
dig +short TXT _dmarc.acme.com

Step 3: Read the reports and fix failing senders

Aggregate reports arrive as zipped XML. Each <record> shows a sending IP, a count, and whether SPF and DKIM aligned.

xml
<record>
  <row>
    <source_ip>203.0.113.25</source_ip>
    <count>412</count>
    <policy_evaluated>
      <disposition>none</disposition>
      <dkim>fail</dkim>
      <spf>pass</spf>
    </policy_evaluated>
  </row>
  <identifiers><header_from>acme.com</header_from></identifiers>
</record>

Here SPF passes but DKIM fails for 412 messages. The usual causes for outbound: a sequencer sending without your DKIM key, or a new mailbox on a secondary domain with no record. Fix each IP until every legitimate source passes at least one aligned check.

Outbound tip: send from a secondary domain, not your main one. Publish DMARC on each sending domain, and keep the main domain on p=reject.

Step 4: Move to enforcement

Once the reports show only legitimate sources passing, tighten in stages. Wait a week between changes.

text
v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc-reports@acme.com
v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc-reports@acme.com
v=DMARC1; p=reject; rua=mailto:dmarc-reports@acme.com

If a legitimate sender starts landing in spam after a change, drop pct or go back one step. Read the reports again before you retry.

Step 5: Keep the list clean so the policy has a chance

DMARC proves who you are. It does not stop recipients from marking you as spam, and Google says frequent spam reports lower domain reputation over time (September 2026). Bounces from bad addresses hurt the same domain.

Size a verified-only audience first. A REST count with count=true in the query and limit 1 spends no records. The MCP tool leadocean_count_leads gives the same answer for free.

bash
curl -s "https://api.leadocean.io/v1/people/search?count=true" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "emailStatus": ["verified"],
    "limit": 1
  }'

Then pull a small first batch of 25 and send it from the new domain slowly:

bash
curl -s https://api.leadocean.io/v1/people/search \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "emailStatus": ["verified"],
    "limit": 25
  }'

That spends 25 records. A search status of verified is an upper bound, so confirm each address at enrichment. LeadOcean does not refund bounces, so read email_status before every send.

What you get

Step 1 on a domain with a policy returns the record as plain text. This is a sample, not a LeadOcean record.

text
"v=DMARC1; p=reject; rua=mailto:dmarc-reports@acme.com; adkim=r; aspf=r"

The free count from leadocean_count_leads ran on 2026-10-21. Filters: jobLevel VP, jobFunction Sales & Business Development, country US, emailStatus verified only.

json
{
  "total": 31141,
  "totalIsExact": true,
  "distinctCompanies": 18563,
  "credits": 0
}

That is a verified-only count, not the full mailable count. The default mailable count also includes catch-all addresses, so it is larger.

Response shape from the API reference (placeholder values), for the first search result:

json
{
  "success": true,
  "data": [
    {
      "profile_data": {
        "profile_full_name": "Jane Doe",
        "profile_headline": "VP Sales at Acme"
      },
      "contact_data": {
        "has_email": true,
        "email_status": "verified",
        "email_type": "work",
        "contact_current_experiences": [
          { "job_title": "VP Sales", "company_name": "Acme", "company_domain": "acme.com" }
        ]
      },
      "meta": { "source_ids": { "person_id": "1234567" }, "fetched_at": "2026-10-01" }
    }
  ],
  "meta": { "count": 1, "limit": 25, "credits": 1, "nextCursor": "..." }
}

Troubleshooting

ErrorCauseFix
dig returns nothing for _dmarcRecord at the wrong host, or DNS not propagatedPublish at _dmarc.acme.com, not the apex. Wait and retry
Gmail rejects with 5.7.26Message unauthenticatedPublish SPF, DKIM and DMARC and re-test
No reports arriverua address wrong or mailbox blocks attachmentsUse a plain mailbox and check rua=mailto: spelling
401 from LeadOceanMissing or wrong x-api-keyCheck $LEADOCEAN_API_KEY is exported
429 from LeadOceanAbove the plan rate limit (5 a second on Free, 200 on Agency)Slow down and honour Retry-After
402 from LeadOceanFree plan's 1,000 records spentCounts stay free. Move to Solo at $97 a month. See pricing
Empty search resultsEnum value spelled wrong, such as vp for jobLevelUse VP, or check leadocean_list_enum_values

FAQ

What DMARC policy should I use for cold email?

Start with p=none for 2 to 4 weeks. Move to quarantine, then reject, once reports show legitimate senders passing.

Does DMARC improve deliverability?

It proves your identity and is required by Gmail for bulk senders. It does not fix bad lists or spammy copy. See outbound email tools explained.

Do I need DMARC on every sending domain?

Yes. Each domain you send from needs its own record, including secondary domains.

How does list quality connect to DMARC?

Bounces and spam complaints hit the same domain reputation that DMARC protects. Filter to emailStatus: ["verified"] and see personalizing outbound emails. A worked drafting flow is in 5 unique personas.

More guides are in the blog hub. Plans are on /pricing.

Send to addresses that deserve your domain. Filter to verified emails.

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →