XiaTools

Fetching DMARC Policy Data Across Thousands of Domains at Once

By Rohit, founder of XiaTools · Updated 19 Oct 2026

Fetching DMARC policy data across thousands of domains at once is essential for security teams managing large portfolios, conducting supply chain audits, or monitoring brand impersonation risks. Because Domain-based Message Authentication, Reporting, and Conformance (DMARC) records reside in the public Domain Name System as TXT records, you can programmatically query them at scale using command-line automation and scripting. For quick, single-domain validations or ad-hoc troubleshooting, you can use the DMARC Checker tool to instantly inspect individual records, policy enforcement levels, and reporting URIs.

Executing this process across thousands of targets requires handling DNS lookup limits, rate-limiting, and variations in record formatting. This guide covers the precise methodologies, scripts, and syntax required to extract, parse, and analyze DMARC data from enterprise-scale domain lists safely and efficiently.

Understanding DMARC Record Architecture at Scale

Before launching a mass-query operation, you need to understand how DMARC records are structured in the DNS. A DMARC record is always published as a TXT resource record at a specific subdomain: _dmarc.example.com. It begins with the mandatory version tag (v=DMARC1) and includes policy instructions such as p=none, p=quarantine, or p=reject.

When you scale this query process to thousands of domains, you will inevitably encounter malformed entries, missing records, or CNAME chains. A robust fetching mechanism must account for these variations without crashing or hanging indefinitely on unresponsive nameservers.

Standard DMARC Tag Reference

Tag Description Common Values Importance at Scale
v Protocol Version DMARC1 Mandatory for validation.
p Requested Policy none, quarantine, reject Core metric for security posture audits.
sp Subdomain Policy none, quarantine, reject Inherits from p= if omitted.
rua Aggregate Reports URI mailto:reports@example.com Critical for identifying mail streams.
pct Percentage of messages 0 to 100 Shows phased rollouts of strict policies.

Method 1: Bulk Scripting with PowerShell

For Windows environments or cross-platform PowerShell Core installations, you can iterate through a CSV file containing your target domains and query their DMARC records using native DNS resolution cmdlets.

Prepare a comma-separated values file named domains.csv with a single column header: Domain.

Domain
example.com
example.org
example.net

Run the following PowerShell script to query the records, extract the policy, and export the results to a structured report:

$inputCsv = "./domains.csv"
$outputCsv = "./dmarc_audit_results.csv"
$results = @()

foreach ($row in (Import-Csv $inputCsv)) {
    $targetDomain = "_dmarc.$($row.Domain)"
    $dmarcRecord = "Not Found"
    $policy = "Unknown"
    
    try {
        $dnsQuery = Resolve-DnsName -Name $targetDomain -Type TXT -ErrorAction Stop
        foreach ($record in $dnsQuery) {
            if ($record.Strings -match "v=DMARC1") {
                $dmarcRecord = $record.Strings -join ""
                if ($dmarcRecord -match "p=([a-z]+)") {
                    $policy = $Matches[1]
                }
                break
            }
        }
    }
    catch {
        $dmarcRecord = "Lookup Failed"
        $policy = "None"
    }
    
    $results += [PSCustomObject]@{
        Domain = $row.Domain
        DMARC_Record = $dmarcRecord
        Policy = $policy
    }
}

$results | Export-Csv -Path $outputCsv -NoTypeInformation
Write-Host "Audit complete. Results saved to $outputCsv"

Method 2: High-Performance Bulk Fetching with Bash and Dig

On Unix-like systems (Linux and macOS), combining standard tools like cat, dig, and awk provides a fast, lightweight solution for processing large text lists.

Create a plain text file named domains.txt with one domain per line:

example.com
example.net
example.org

Execute the following bash loop to query each domain sequentially, extracting only the DMARC TXT string:

#!/bin/bash
INPUT_FILE="domains.txt"
OUTPUT_FILE="dmarc_results.csv"

echo "Domain,DMARC_Record" > "$OUTPUT_FILE"

while IFS= read -r domain; do
    # Skip empty lines or comments
    [[ -z "$domain" || "$domain" =~ ^# ]] && continue
    
    # Query DNS for the _dmarc TXT record
    record=$(dig +short TXT "_dmarc.$domain" | grep -i "v=DMARC1" | tr -d '"')
    
    if [ -z "$record" ]; then
        record="No DMARC Record Found"
    fi
    
    echo "$domain,\"$record\"" >> "$OUTPUT_FILE"
done < "$INPUT_FILE"

echo "Batch fetch complete. Output saved to $OUTPUT_FILE"

For massive lists containing tens of thousands of entries, sequential execution will take too long. You can parallelize this operation using GNU parallel:

cat domains.txt | parallel -j 50 'echo -n "{}," ; dig +short TXT _dmarc.{} | grep -i "v=DMARC1" | tr -d "
" ; echo ""' > parallel_dmarc_output.csv

Method 3: Python Scripting for Enterprise Scale

Python offers robust error handling, asynchronous processing, and advanced parsing capabilities via libraries like dnspython. This approach is ideal for production environments where you need strict timeout controls and detailed logging.

First, install the required library:

pip install dnspython

Use the following script to asynchronously or sequentially fetch and parse DMARC records, handling exceptions cleanly:

import csv
import dns.resolver

# Configure resolver with custom timeouts
resolver = dns.resolver.Resolver()
resolver.timeout = 2.0
resolver.lifetime = 2.0

def check_dmarc(domain):
    target = f"_dmarc.{domain}"
    try:
        answers = resolver.resolve(target, 'TXT')
        for rdata in answers:
            txt_string ="".join([s.decode('utf-8') for s in rdata.strings])
            if "v=DMARC1" in txt_string:
                return txt_string
        return "DMARC Tag Missing"
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
        return "Record Not Found"
    except dns.exception.Timeout:
        return "DNS Timeout"
    except Exception as e:
        return f"Error: {str(e)}"

# Process input and output
input_file = 'domains.csv'
output_file = 'python_dmarc_results.csv'

with open(input_file, mode='r', encoding='utf-8') as infile, \
     open(output_file, mode='w', newline='', encoding='utf-8') as outfile:
    reader = csv.DictReader(infile)
    writer = csv.writer(outfile)
    writer.writerow(['Domain', 'DMARC_Record'])
    
    for row in reader:
        domain = row['Domain'].strip()
        if domain:
            result = check_dmarc(domain)
            writer.writerow([domain, result])

print("Python batch processing complete.")

Handling Edge Cases and DNS Rate Limiting

When querying thousands of domains, you will inevitably run into operational roadblocks that skew your dataset or get your querying IP address rate-limited by public resolvers.

1. Public Resolver Throttling

Using hardcoded public resolvers like 8.8.8.8 or 1.1.1.1 for thousands of rapid queries can trigger rate-limiting, resulting in false DNS Timeout errors. To prevent this, configure your scripts to use your local corporate caching DNS resolver, or rotate through a pool of public resolvers.

2. CNAME Redirection

Some organizations host their DMARC records on a separate management subdomain using a CNAME record instead of a direct TXT record. Standard TXT queries will fail on these domains. Ensure your querying logic follows CNAME chains or use tools that automatically resolve CNAME flattening before looking for the TXT string.

3. Syntax Variations and Whitespace

Administrators often introduce typos, extra spaces, or incorrect casing (e.g., V=DMARC1 instead of v=DMARC1). Always perform case-insensitive matching during your parsing phase to avoid missing records that are technically present but syntactically unorthodox.

Step-by-Step Checklist for Large-Scale DMARC Audits

  1. Clean Your Domain List: Remove duplicates, trailing whitespace, and invalid characters from your input file.
  2. Configure Timeouts: Set strict query timeouts (1 to 3 seconds) to prevent scripts from hanging on dead or misconfigured domains.
  3. Select Your Tooling: Choose Bash/Parallel for quick Linux tasks, PowerShell for Windows environments, or Python for advanced logging and error handling.
  4. Run a Test Batch: Execute your script against a controlled list of 10 known domains (such as example.com and your own internal domains) to verify output formatting.
  5. Execute Full Scan: Run the batch script and monitor network utilization and local resolver health.
  6. Analyze Posture: Filter the output CSV by policy type (p=none, p=quarantine, p=reject) to identify vulnerable domains needing remediation.

Common Mistakes and How to Fix Them

  • Mistake: Querying the apex domain directly (example.com) instead of the required DMARC subdomain (_dmarc.example.com).
    • Fix: Ensure your script automatically prepends _dmarc. to every target string before sending the DNS query.
  • Mistake: Assuming every TXT record returned is a DMARC record.
    • Fix: Filter response strings strictly for the mandatory signature v=DMARC1 to ignore SPF records, domain verification tokens, and site ownership strings.
  • Mistake: Crashing the script when encountering a domain with multiple TXT records.
    • Fix: Iterate through all returned TXT resource records in the DNS response array until you locate the one containing the DMARC tag.

Frequently asked questions

Why do some domains fail to return a DMARC record during bulk checks?

A failure usually occurs because the domain lacks a DMARC record entirely, the target nameserver is unresponsive, or your querying IP address hit a rate limit on public DNS resolvers. Additionally, strict timeouts in your script can cause slow-responding DNS servers to register as failures.

Can I check DMARC policies behind a CNAME record?

Yes, but standard TXT queries alone may fail if the target domain uses a CNAME for its `_dmarc` entry. Your script or DNS client must be configured to follow CNAME chains to resolve the final target TXT resource record.

How can I prevent my IP address from being blocked during mass DNS queries?

To prevent blocking, avoid flooding public resolvers with unthrottled requests. Use a local caching DNS resolver, implement polite request delays or rate-limiting within your scripts, or distribute queries across a pool of different recursive DNS providers.

What is the difference between p=none, p=quarantine, and p=reject policies?

A policy of `p=none` is used for monitoring only, where failing emails are still delivered and reports are sent to the domain owner. `p=quarantine` instructs receiving mail servers to send failing emails to the spam folder, while `p=reject` drops unauthorized emails outright.

How do I handle malformed DMARC records when parsing results at scale?

Your parsing script should use regular expressions or string matching to isolate the specific policy tags rather than assuming a rigid record structure. Log any records that contain `v=DMARC1` but fail strict policy extraction into a separate review file for manual inspection.

Related articles

Free tools