If you run cold-email campaigns, manage multiple client domains, or oversee email infrastructure — you've probably experienced the "sent but never opened" problem. The mail landed in spam, or worse, got silently rejected.
The root cause is almost always DNS posture: missing SPF, lax DMARC, or no DKIM signing. But manually checking eight different record types across dozens of domains is tedious and error-prone.
Here's how to automate the whole audit with a single API call — and what it actually costs.
What a deliverability audit checks
Every domain that sends email needs these records configured correctly:
- MX — mail exchange records (can the domain receive mail?)
-
SPF — which IPs are authorized to send (
v=spf1 include:_spf.google.com -all) -
DKIM — cryptographic signing keys (
<selector>._domainkey.<domain>) -
DMARC — enforcement policy (
p=none/quarantine/reject) - DNSSEC — DNS chain of trust
- MTA-STS — forced TLS on inbound SMTP (RFC 8461)
- TLS-RPT — failure reporting endpoint (RFC 8460)
- BIMI — branded inbox logo
A gap in any of these reduces deliverability or exposes the domain to spoofing.
Step 1: Get the actor
The Email Deliverability & Domain Posture Auditor is an Apify actor with pay-per-event pricing. You can call it via the Apify API, the JavaScript/TypeScript SDK, or the Python SDK.
Step 2: Prepare your domain list
Collect your domains in a JSON array:
{
"domains": ["example.com", "my-startup.io", "client-portfolio.org"],
"checkMTASTS": true,
"timeoutSecs": 10
}
dkimSelectors is optional — nine common selectors are probed by default (google, default, selector1, selector2, s1, k1, mail, amazon, sendgrid). If your provider uses a non-standard selector, pass it explicitly.
Step 3: Call the API
Using curl:
curl -s -X POST "https://api.apify.com/v2/acts/gp005~email-deliverability-auditor/runs" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{"domains":["example.com","my-startup.io","client-portfolio.org"],"checkMTASTS":true}'
Using the Python SDK:
from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("gp005/email-deliverability-auditor").call(
run_input={"domains": ["example.com", "my-startup.io", "client-portfolio.org"]}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(f"{item['domain']}: score {item['posture_score']}/100 ({item['posture_grade']})")
if item.get("top_fix"):
print(f" -> {item['top_fix']}")
Step 4: Interpret the results
Each domain returns one dataset row with 22 fields. The headline values are:
-
posture_score— 0-100 numeric score -
posture_grade— A through F -
issues— sorted by severity (critical → info), each with an exactfixstring -
top_fix— the single highest-impact change - Per-record detail: parsed
spf(includes,all_mech, qualifier, unsafe flag), parseddmarc(policy,subdomain_policy,pct,aggregate_reports,enforced),dkim(selectors_probed,keys_found, found keys),dnssec,mta_sts(policy mode orabsent),tls_rptandbimiaspresent/absent
Note the shapes: mta_sts, tls_rpt and bimi come back as strings, not nested objects — don't write item['mta_sts']['mode'].
A score below 60 means critical gaps exist. Fix them in the order the issues array presents them — the first item is the highest-impact change.
Step 5: Apply fixes
Each fix field contains the exact TXT record or configuration change needed:
{
"severity": "critical",
"area": "DMARC",
"message": "No DMARC record found",
"fix": "Publish a DMARC TXT record at _dmarc.example.com: \"v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@example.com\""
}
What one real run looks like
A run on build 0.1.7 in September 2026 audited two domains in 2.66 seconds wall-clock and returned:
-
cloudflare.com— 92/100 (A), 1 issue, 2 DKIM keys found at the nine default selectors, TLS-RPT and BIMI present, DNSSEC true, MTA-STS absent -
google.com— 73/100 (C), 4 issues, 0 DKIM keys found at those same nine selectors
That second row is the honest limitation of the whole approach, and it's worth internalising: Google signs its mail with a selector that isn't in any default list, and no DNS query will tell you what it is. If you don't pass your selector, a domain can report dkim_found: false while signing every message it sends. Always confirm your actual selector with your mail provider before trusting that field.
Pricing (measured, not marketing)
Pay-per-event, current list prices (Apify free plan):
| Event | Unit | Price |
|---|---|---|
actor-start |
per run | $0.01 |
domain-audited |
per domain fully audited | $0.03 |
issue-flagged |
per issue flagged (capped at 8/domain) | $0.01 |
So the real numbers:
-
1 domain with ~3 issues:
$0.01 + $0.03 + $0.03= $0.07 -
100 domains with ~3 issues each:
$0.01 + $3.00 + $3.00= $6.01 (about 6 cents per domain)
Apify applies volume discounts by subscription tier — domain-audited drops to $0.027 (BRONZE), $0.024 (SILVER), $0.021 (GOLD), $0.018 (PLATINUM) and $0.015 (DIAMOND), so heavy users pay roughly half at the top tier.
Two pricing behaviours worth knowing: failed lookups aren't charged (a domain that can't be resolved returns a structured error row, not a billable audit), and there is no free tier on the actor itself — a single domain costs about seven cents.
Compare that to $15-50 per domain from a traditional deliverability vendor.
FAQ
Can I run this on a schedule? Yes — Apify Schedules let you run weekly or monthly with webhook delivery to Slack, Teams, or your own endpoint.
Does this check live inbox placement? No — this is a DNS-posture audit only. It never opens an SMTP connection and never sends mail. Pair it with an SMTP-probe or a seed-list tool for bounce and placement data.
What if my domains use custom DKIM selectors? Pass them in the dkimSelectors array and the actor probes those in addition to the nine defaults.
Do you detect duplicate DMARC records? Not currently — the actor parses the first v=DMARC1 record it finds and returns its raw value. With duplicate records some receivers discard the set entirely, so check manually if you suspect a migration leftover.
Is the audit data stored? Results go to your Apify dataset. You control retention. Inputs are domain names only; nothing personal is collected.
The posture score is a heuristic weighting protocol-level gaps — it knows nothing about your sending volume, engagement or reputation history. Use it to prioritise fixes, not to predict inbox placement.
Top comments (0)