# Domain Security & Email Deliverability — DNS, SPF, DMARC, DKIM (`ryanclinton/dns-record-lookup`) Actor

Audit a domain portfolio for email spoofing, broken SPF, DMARC enforcement, DNSSEC and shadow-SaaS senders. Returns a posture score, what to fix first, vendor dependencies and drift alerts — not just records. Bulk DNS / SPF / DMARC / DKIM lookup, no API keys.

- **URL**: https://apify.com/ryanclinton/dns-record-lookup.md
- **Developed by:** [Ryan Clinton](https://apify.com/ryanclinton) (community)
- **Categories:** Developer tools, SEO tools
- **Stats:** 27 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 dns looked ups

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Domain Security, Deliverability & Governance Intelligence

![Domain Security, Deliverability & Governance Intelligence — find what's spoofable and what to fix first](https://apifyforge.com/readme-assets/ryanclinton-dns-record-lookup/hero.png)

*DNS, SPF, DMARC, DKIM & email-auth intelligence for a whole domain portfolio — formerly DNS Record Lookup.*

**Find in one run:**

✓ Spoofable domains   ✓ Forgotten SaaS senders   ✓ Silent SPF failures   ✓ Vendor dependencies   ✓ Single points of failure   ✓ What to fix first

***

### Find out in 5 seconds

```json
// input
{ "domains": ["example.com"] }
```

```json
// what you learn
{
  "domain": "example.com",
  "postureGrade": "F",
  "spoofable": true,
  "recommendedAction": "fix-now",
  "topFix": "Enable DMARC enforcement"
}
```

Most DNS tools tell you what records exist. This actor tells you **what is broken, what is risky, what changed, and what to fix first** — across one domain or a portfolio of thousands. Deterministic, no LLM, no third-party keys.

***

### What nobody realises about their domains

Marketing connected Mailchimp. Sales connected HubSpot. Support connected Zendesk.

Years later, nobody remembers any of it — but the authorisation to send email as your company is still there, sitting in your SPF and DKIM records. **This actor finds it.**

More broadly, most organisations cannot answer these questions about their own domains:

- Which vendors can send email on our behalf?
- Which SaaS platforms are still authorised that nobody remembers connecting?
- Which of our domains are spoofable right now?
- Which providers are single points of failure?
- What changed in the last 30 days?

This actor answers all five — from records that are already public, that you've simply never correlated.

***

### Questions this actor answers

> ✓ Is this domain **spoofable**?
> ✓ What should we **fix first**?
> ✓ Which **vendors** can send email for us?
> ✓ What **SaaS platforms** are still connected?
> ✓ Which domains are **operationally risky**?
> ✓ Which **12 of our 500 domains** need attention today?
> ✓ **What changed** and why does it matter?
> ✓ Which **providers** are we dependent on?
> ✓ Who **owns** each domain's setup?
> ✓ Is this **acquisition target** a mess?

***

### Outcomes by team

| Team | What they get |
|------|---------------|
| **Security** | Find spoofable domains, detect control regressions, surface shadow SaaS senders. |
| **MSPs** | Prioritise remediation across clients, generate executive reports, monitor portfolios on a schedule. |
| **Deliverability** | Fix SPF PermErrors, raise DMARC enforcement, discover risky third-party senders. |
| **M\&A / due diligence** | Assess a target's email-security maturity, vendor sprawl and infrastructure risk before the deal closes. |

***

### Used for

Domain portfolio audits · MSP client reporting · security assessments · email-deliverability reviews · M\&A due diligence · scheduled DNS / email-auth monitoring · vendor discovery · SaaS rationalisation.

***

![Four features — spoofability verdict, shadow-SaaS discovery, fix-first roadmap, drift and regression alerts](https://apifyforge.com/readme-assets/ryanclinton-dns-record-lookup/feature-callouts.png)

### Why this is different

- Most **DNS tools** show you *records*.
- Most **DMARC tools** show you *email authentication*.
- Most **monitoring tools** show you *changes*.

This actor shows you **risk, ownership, dependencies, governance, and what to fix first** — the decision layer the others leave to you. In detail, where a DNS tool tells you what records exist, this tells you:

- **What is risky** — spoofability, takeover exposure, operational fragility.
- **What is broken** — silent SPF PermErrors, dead MX targets, unenforced DMARC.
- **What changed** — and whether it's a regression worth paging someone over.
- **What to fix first** — findings ranked by security recovered per hour of work.
- **Who you depend on** — every vendor authorised to send, and which are single points of failure.
- **Whether it's still needed** — the forgotten SaaS senders you can probably remove.
- **What would break if it failed** — per-domain blast radius for email, web and identity.

***

### One example that lands

Point it at a single corporate domain:

```json
{ "domains": ["corp.example"] }
```

```json
// vendors — who can send email as corp.example
{
  "vendors": [
    { "name": "Microsoft 365", "kind": "mailbox", "sources": ["mx", "spf"] },
    { "name": "Proofpoint",    "kind": "gateway", "sources": ["mx"] },
    { "name": "SendGrid",      "kind": "esp",     "sources": ["spf"] },
    { "name": "Mailchimp",     "kind": "sender",  "sources": ["dkim"] }
  ],
  "thirdPartySenders": ["SendGrid", "Mailchimp"],
  "cleanupCandidates": [
    { "vendor": "Mailchimp", "reason": "A DKIM selector exists but the vendor is not in SPF — may be a stale selector.", "recommendation": "review" }
  ]
}
```

Nobody remembered Mailchimp was still authorised to send email as the company. The actor found it in the DKIM records — and flagged it for review — in one call.

Now point it at all 143 of an MSP client's domains in one run — the closing `summary` record is the prioritised work queue:

```json
{
  "executiveSummary": { "domains": 143, "critical": 7, "spoofable": 12, "averagePostureScore": 64 },
  "portfolioRanking": [
    { "priority": 1, "domain": "client-a.com", "spoofable": true, "topRisk": "NO_DMARC" },
    { "priority": 2, "domain": "client-b.net", "spoofable": true, "topRisk": "SPF_LOOKUP_LIMIT_EXCEEDED" }
  ]
}
```

143 client domains in, a ranked remediation queue out — drop it straight into the monthly client report.

***

### What you get from one call

Every domain comes back with the **decision fields first** — `postureScore` + grade, `spoofable`, `recommendedAction`, `topFix` — then the supporting intelligence:

- **Email authentication** — SPF policy + the RFC 7208 **10-lookup-limit check** (catches the silent `PermError` that breaks SPF) + include-tree map; **DMARC maturity** (0–100, monitoring → enforced); DKIM; **MTA-STS / TLS-RPT / BIMI**.
- **DNSSEC** — signed / validated / unsigned (via DNS-over-HTTPS).
- **DNS resilience & attack surface** — redundancy, single-point-of-failure, dangling-CNAME / subdomain-takeover risk, dead-MX targets.
- **Vendor & governance** — vendor inventory + **shadow SaaS**, inferred **ownership**, **cleanup candidates**, **complexity / operational risk / maintainability / blast radius**.
- **Prioritisation & reporting** — **security gradecard** (5 category grades), **remediation roadmap** (reduction-per-effort), **security debt**, account **benchmark**, and a run-level **executive summary**, **portfolio ranking** and **vendor dependency graph**.
- **Compliance** — Google / Microsoft bulk-sender mapping (DNS-checkable portion) + a stable findings catalogue (`DNS-NNN`).
- **Monitoring** — opt-in cross-run drift with **regression alerts**, **change narratives**, baselines and churn.

The full raw record set (A/AAAA/MX/NS/TXT/CNAME/SOA) is still on every record. You get the decision and the data.

*(Formerly DNS Record Lookup — it still does every lookup `dig` does; the lookups are now the data layer underneath the intelligence.)*

***

### Problems this finds

- **Your domain is spoofable.** No enforced DMARC means attackers can impersonate your domain in email today.
- **Forgotten SaaS still has the keys.** Marketing connected Mailchimp in 2021; it can still send as you, and nobody remembers it. The actor surfaces it as a third-party sender and a cleanup candidate.
- **SPF is silently broken.** Mail that should authenticate fails because SPF exceeds the RFC 7208 10-lookup limit and returns `PermError` — invisible until deliverability drops.
- **Vendor concentration risk.** Dozens of domains depend on one mail gateway; a single outage takes them all down. The dependency graph and `singlePointsOfFailure` make it visible.
- **A control was quietly removed.** DMARC went from `reject` to `none` after two years of stability — the regression alert and change narrative catch it.

***

### Records vs decisions

Most DNS tools return things. This returns work to do.

| A raw DNS lookup gives you | This actor also gives you |
|---|---|
| `dmarcRecord: "v=DMARC1; p=none"` | "DMARC is monitoring-only — your domain is still spoofable. Move to `p=reject`." |
| `spfRecord: "v=spf1 include:a include:b … ~all"` | "SPF is at 11 DNS lookups — over the limit. Receivers return PermError and SPF fails." |
| `mxRecords: [...]`, `nsRecords: [...]` | "Single self-hosted MX, one nameserver — no redundancy." |
| `cnameRecords: ["x.s3.amazonaws.com"]` | "CNAME points to an unclaimed target — possible subdomain-takeover exposure." |
| 13 fields you still have to interpret | `postureScore: 41`, `riskLevel: "critical"`, `recommendedAction: "fix-now"` |

#### How this compares

DNS and email tools cluster into a few categories. Each is good at its slice; this actor spans the decision + governance layer on top of all of them.

| Capability | DNS diagnostic tools (dig, nslookup, MXToolbox) | DMARC platforms (EasyDMARC, Dmarcian) | Exposure-mapping platforms (Hardenize) | This actor |
|---|:---:|:---:|:---:|:---:|
| Raw record lookup | ✓ | partial | ✓ | ✓ |
| SPF lookup-limit detection | partial | ✓ | ✓ | ✓ |
| DMARC maturity scoring | ✗ | ✓ | ✓ | ✓ |
| MTA-STS / TLS-RPT / BIMI | partial | partial | ✓ | ✓ |
| DNSSEC | partial | ✗ | ✓ | ✓ |
| Vendor inventory + shadow-SaaS | ✗ | ✗ | partial | ✓ |
| Operational risk + blast radius | ✗ | ✗ | partial | ✓ |
| Remediation roadmap (fix-first) | ✗ | partial | ✗ | ✓ |
| Portfolio ranking + dependency graph | ✗ | partial | partial | ✓ |
| Runs in your own pipeline (Apify API, no SaaS seat) | ✗ | ✗ | ✗ | ✓ |

Comparison reflects the typical focus of each category; specific products evolve their feature sets over time.

***

### Why this exists

Running `dig` against one domain is easy. Auditing the email security and DNS resilience of 50, 500 or 5,000 domains — and knowing *which* of them to fix first — is not. Reading a raw SPF string and deciding whether it is safe takes expertise; reading `recommendedAction: "fix-now"` and `whyFlagged` takes a second.

This actor does the interpretation for you, deterministically. The same domain always produces the same score — no LLM, no randomness, no external API. It runs on Node's native DNS resolver, so there are no third-party keys, no rate limits, and nothing to manage.

***

### How it works

1. **Clean + de-duplicate** the input domains (protocol, path, port and `www.` are stripped; duplicates removed).
2. **Resolve** A, AAAA, MX, NS, TXT, CNAME and SOA in parallel, plus SPF / DMARC / DKIM when email-security checks are on.
3. **Analyse** the email-authentication posture (SPF policy + lookup limit, DMARC strength, DKIM), the DNS resilience (redundancy, takeover risk), and the providers.
4. **Score** each domain into a posture (0–100) + grade, derive a `riskLevel`, stable `riskFlags`, and a `recommendedAction`, with a plain-English `whyFlagged` and `summary`.
5. **Stream** each domain's record as it finishes, then emit a run-level summary (grade distribution, spoofable count, worst domains, prioritised recommendations).

Every domain's record is pushed the moment it is ready, so the first results appear within a second or two even on large runs.

![The intelligence stack — from your domain list through DNS resolution, authentication analysis, vendor detection, governance scoring and prioritisation to a decision](https://apifyforge.com/readme-assets/ryanclinton-dns-record-lookup/intelligence-layers.png)

***

### What you learn

![Sample output — every domain with its posture grade, spoofable verdict, risk level, recommended action and top fix](https://apifyforge.com/readme-assets/ryanclinton-dns-record-lookup/output-table.png)

#### Executive view — one domain at a glance

The fields most people act on sit at the top of every record:

```json
{
  "domain": "corp.example",
  "postureGrade": "D",
  "spoofable": true,
  "riskLevel": "high",
  "recommendedAction": "fix-now",
  "topFix": "Advance DMARC from p=none to p=quarantine, then p=reject.",
  "ownership": { "likelyOwner": "marketing", "confidence": "medium" },
  "maintainability": { "score": 58, "level": "moderate" }
}
```

#### Portfolio view — the whole estate in one summary record

Run it against your full list and the closing `summary` record is the MSP / exec report:

```json
{
  "recordType": "summary",
  "domainsAnalyzed": 527,
  "averagePostureScore": 71,
  "executiveSummary": { "domains": 527, "critical": 9, "high": 22, "spoofable": 31, "averagePostureScore": 71 },
  "portfolioRanking": [
    { "priority": 1, "domain": "legacy.example.net", "postureScore": 18, "spoofable": true, "topRisk": "NO_DMARC" }
  ],
  "criticalVendors": [{ "vendor": "Proofpoint", "domains": 214, "sharePct": 41 }],
  "singlePointsOfFailure": [{ "vendor": "Proofpoint", "affectedDomains": 214, "sharePct": 41 }],
  "recommendations": [
    "21 domain(s) have no DMARC record — they are spoofable. Publish DMARC, then enforce p=reject.",
    "Portfolio concentrated on Proofpoint (41% of domains) — a single vendor outage has wide blast radius."
  ]
}
```

527 domains in, "fix these 9 first and you depend on Proofpoint" out.

#### Detailed view — the full record

A representative domain record, abridged to the high-signal blocks (a real record carries more):

```json
{
    "recordType": "domain",
    "domain": "example.com",
    "postureScore": 41, "postureGrade": "E", "spoofable": true,
    "riskLevel": "critical", "recommendedAction": "fix-now",
    "topFix": "Advance DMARC from p=none to p=quarantine, then p=reject.",
    "riskFlags": ["DMARC_MONITOR_ONLY", "SPF_LOOKUP_LIMIT_EXCEEDED", "SPOOFABLE"],
    "whyFlagged": "DMARC is monitoring-only (p=none) — spoofed mail is still delivered.",
    "emailAuth": {
        "spf": { "policy": "softfail", "resolvedLookupCount": 11, "lookupRisk": "exceeds-limit", "distinctVendors": 6 },
        "dmarc": { "policy": "none", "enforced": false, "maturityScore": 36, "maturityLevel": "monitoring" },
        "dkim": { "present": true },
        "spoofable": true, "spoofableReason": "DMARC p=none does not reject spoofed mail.",
        "recommendedActions": ["Reduce SPF DNS lookups below 10.", "Advance DMARC to p=quarantine, then p=reject."]
    },
    "dnssec": { "status": "unsigned" },
    "gradecard": { "emailAuthentication": "E", "transportSecurity": "C", "dnsResilience": "B", "attackSurface": "A+", "monitoringReadiness": "B", "overall": "C" },
    "remediationPlan": [
        { "priority": 1, "findingId": "DNS-003", "title": "Enable DMARC enforcement", "effort": "low", "scoreGain": 16 }
    ],
    "securityDebt": { "score": 41, "level": "medium" },
    "benchmark": { "percentile": 34, "band": "below-average", "population": "domains analysed on this account" },
    "vendors": { "count": 3, "thirdPartySenders": ["SendGrid", "Mailchimp"], "risk": "medium" },
    "ownership": { "likelyOwner": "marketing", "confidence": "medium" },
    "cleanupCandidates": [{ "vendor": "Mailchimp", "reason": "DKIM selector with no SPF — may be stale.", "recommendation": "review" }],
    "complexity": { "score": 45, "level": "high" },
    "operationalRisk": { "score": 22, "level": "medium" },
    "maintainability": { "score": 58, "level": "moderate" },
    "blastRadius": { "email": "high", "web": "medium", "identity": "low" }

    // … plus modernEmailSecurity (MTA-STS/TLS-RPT/BIMI), mailArchitecture, dnsHealth, providers,
    //    compliance, findings[], domainRole, the raw A/AAAA/MX/NS/TXT/CNAME/SOA records, and nextActions[].
}
```

The closing `summary` record (shown as the "Portfolio view" above) carries the run-level rollup: `executiveSummary`, `portfolioRanking`, grade distribution, the vendor dependency graph, `criticalVendors`, `singlePointsOfFailure` and prioritised `recommendations`. It is also mirrored to the key-value store under `SUMMARY`.

**Headline output fields**

| Field | Description |
|-------|-------------|
| `recordType` | `domain` | `summary` | `error`. |
| `resolved` | `false` when the domain returned no DNS records (likely does not exist / misspelled). Unresolved domains are reported but not billed and excluded from the posture statistics. |
| `postureScore` / `postureGrade` | 0–100 composite + letter grade (A+ → F). Higher is better. |
| `riskLevel` | `critical` | `high` | `medium` | `low` | `none`. |
| `riskFlags` | Stable tokens: `NO_DMARC`, `DMARC_MONITOR_ONLY`, `DMARC_NOT_ENFORCED`, `NO_SPF`, `SPF_PASS_ALL`, `SPF_NEUTRAL`, `SPF_LOOKUP_LIMIT_EXCEEDED`, `SPF_LOOKUP_LIMIT_NEAR`, `SPOOFABLE`, `NO_DKIM`, `DANGLING_CNAME_RISK`, `SINGLE_POINT_OF_FAILURE`. |
| `recommendedAction` | `fix-now` | `review` | `monitor` | `ok`. |
| `whyFlagged` / `summary` | Plain-English reason + ≤280-char summary line. |
| `emailAuth` | Full SPF (policy + lookup-limit + include tree + vendor count) / DMARC (+ maturity) / DKIM + `spoofable` + `recommendedActions`. |
| `modernEmailSecurity` | MTA-STS (+ mode), TLS-RPT, BIMI (+ VMC), `adoptionScore`. |
| `mailArchitecture` | Inferred inbound provider, security gateway, outbound senders. |
| `dnsHealth` | Redundancy, single-point-of-failure, IPv6, dangling-CNAME risk, dead-MX. |
| `dnssec` | `status`: `signed-validated` / `signed` / `unsigned` / `unknown`. |
| `providers` | DNS provider, mail provider, mail-provider category. |
| `subdomainProtected` | Whether the DMARC policy governs subdomains. |
| `domainRole` | Inferred role: `primary` / `mail-only` / `web-only` / `parked-or-inactive`. |
| `findings` | Stable misconfiguration catalogue (`DNS-NNN` id + severity + title). |
| `compliance` | Google / Microsoft bulk-sender mapping (DNS-checkable portion) + CIS controls. |
| `gradecard` | Five executive category letter-grades + overall. |
| `remediationPlan` | Findings ordered by reduction-per-effort (`priority`, `effort`, `scoreGain`). |
| `securityDebt` | Accumulated unfixed-risk score + level. |
| `benchmark` | Posture percentile vs domains scanned on this account. |
| `topFix` | The single highest-priority remediation for this domain. |
| `vendors` | Vendor inventory (`vendors[]` + `thirdPartySenders[]` shadow SaaS + `count` + `risk`). |
| `ownership` | Inferred owning team (`likelyOwner` + confidence + evidence) from the vendor mix. |
| `cleanupCandidates` | Vendors authorised in SPF/DKIM only — "can I remove this?" review candidates. |
| `complexity` | Structural complexity score + level + drivers. |
| `operationalRisk` | Operational fragility score + level + drivers. |
| `maintainability` | "Is this manageable to operate?" score + level. |
| `blastRadius` | `email` / `web` / `identity` impact-if-it-breaks. |
| `monitoring` | Cross-run drift (only when `watchlistName` is set) — `changeType`, `driftSeverity`, `changeImpactScore`, `regression`, `changes`, `postureScoreDelta`, `trend`. |

***

### Input

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `domains` | string\[] | — | Domains to analyse. Protocol / path / port / `www.` stripped; duplicates removed. |
| `recordTypes` | string\[] | all 7 | Which DNS record types to resolve. |
| `checkEmailSecurity` | boolean | `true` | Resolve SPF, DMARC and DKIM (required for the posture engine). |
| `timeout` | integer | `5000` | Per-query timeout in ms (1,000–30,000). |
| `analysisMode` | select | `balanced` | Re-weights the posture score: `deliverability` (SPF/DMARC/DKIM heaviest), `security` (DNS resilience + takeover risk), `monitoring` (scheduled re-runs), or `balanced`. |
| `outputProfile` | select | `standard` | `minimal` (decision fields only — for Zapier/Make rules), `standard`, or `full` (every diagnostic block). |
| `expandSpfIncludes` | boolean | `false` | Resolve the full SPF include chain to count real DNS lookups (catches PermErrors) and map sending vendors. Adds extra DNS queries; when off, a static lower-bound count is used. |
| `checkDnssec` | boolean | `true` | Query DNSSEC state via Cloudflare DNS-over-HTTPS (no key). Adds one lightweight HTTPS request per domain. |
| `watchlistName` | string | — | Name a watchlist to persist this run's fingerprint and diff it next run — flags DMARC/SPF downgrades, DKIM removal, MX / mail-provider changes, and tracks a posture-score trend. |
| `maxPostureScore` | integer | `100` | Surface (and bill for) only domains scoring at or below this — i.e. only the domains that need attention. |

**Example — audit a portfolio, deliverability lens, surface only the problems:**

```json
{
    "domains": ["example.com", "legacy.example.net", "shop.example.org"],
    "analysisMode": "deliverability",
    "expandSpfIncludes": true,
    "maxPostureScore": 70
}
```

***

### Monitoring (scheduled runs)

Set a `watchlistName` and schedule the actor. On the first run every domain reads `changeType: "new"`. From the second run on, each record carries a `monitoring` block flagging what drifted since last time:

- `dmarc-weakened` (e.g. `reject` → `none`) and `spf-weakened` (e.g. `-all` → `~all`) — **regressions**: a security control was weakened. `monitoring.regression: true`.
- `mx-changed` / `mail-provider-changed` — your mail routing moved.
- `dkim-removed`, `nameservers-changed`, `a-records-changed`.

Each carries a `driftSeverity` plus a numeric `changeImpactScore` (so alert routing can branch on `WHERE monitoring.regression = true` or threshold on the score), and a `trend` block tracking the posture score across runs (improving / declining / stable). Every record also gets:

- `baseline` — `firstSeen` and `daysStable` (how long the config held before this change). A change after 3 days reads very differently from one after 3 years.
- `driftRisk` — `{ level, reason }`, the business-impact framing of the change (a regression elevates to at least `high`).
- `changeFrequency` — `last30Days` / `last90Days` change counts + a `risk` band. Frequently-changing domains are usually unmanaged or mid-migration.
- `changeNarrative` — a paste-ready one-liner with an `importance` tier: *"DMARC enforcement removed after 742 days of stability."* Drops straight into a SOC ticket, a change-review (CAB) note, or a Slack alert.

This turns a one-off lookup into continuous DNS / email-auth monitoring with regression alerting.

***

### Prioritisation & reporting

The actor does not just flag problems — it tells you what to fix first and packages the run for a report.

- **Remediation roadmap** — every record carries a `remediationPlan[]` ordered by **reduction-per-effort**: the fix that recovers the most posture for the least work is `priority: 1`. Each step has `effort` (low/medium/high), `scoreGain`, and the stable `findingId`. Security teams fix queues, not findings.
- **Security gradecard** — five category letter-grades (`emailAuthentication`, `transportSecurity`, `dnsResilience`, `attackSurface`, `monitoringReadiness`) plus `overall`. An executive reads `A / C / A / C / B` in one glance.
- **Security debt** — `securityDebt.score` (0–100) is the accumulated weight of unfixed findings, aged up the longer they stay open (with a watchlist).
- **Benchmark** — `benchmark.percentile` answers "is 72 good?" by comparing against the domains you have scanned on **this account** (it is honestly account-scoped, never a fabricated global figure, and stays `null` until the pool has ≥ 10 samples).
- **Portfolio ranking** — the run `summary` carries a `portfolioRanking[]` (worst domains first, with `priority`) and an `executiveSummary` block (critical / high / spoofable counts + average score) that drops straight into an MSP client report.

***

### Vendor & governance intelligence

Most DNS tools answer "what does this domain look like?". This one also answers "what are we dependent on, and is it manageable?" — entirely from the records already resolved.

- **Vendor inventory** — `vendors` lists every vendor authorised to send or route mail (Microsoft 365, Salesforce, SendGrid, Mailchimp, Zendesk, …), classified by `kind` (mailbox / gateway / esp / sender) and `sources` (mx / spf / dkim). `thirdPartySenders[]` surfaces the **shadow SaaS** — the senders marketing wired up years ago that nobody remembers.
- **Ownership** — `ownership` infers which team likely owns the setup (IT / marketing / developers) from the vendor mix, so you know who to talk to before changing anything. Heuristic and labelled "likely" — ownership isn't literally in DNS.
- **Cleanup candidates** — `cleanupCandidates[]` flags vendors authorised in SPF (or a lone DKIM selector) with no other evidence they're in use — the "can I safely remove this?" review list. It recommends **review**, never blind removal.
- **Vendor dependency graph** — the run `summary` carries `vendorDependencies` (each vendor → which of your domains depend on it), `criticalVendors[]` (vendors ≥ 40% of the portfolio leans on), and `singlePointsOfFailure[]` (vendors ≥ 50% depend on, with the affected domain list). "If our mail gateway has an outage, how many domains go dark?" — answered.
- **Complexity, operational risk & maintainability** — `complexity` scores how complicated the setup is; `operationalRisk` scores how fragile it is to run; `maintainability` collapses both (plus monitoring hooks) into one "is this manageable?" number. Complex, fragile environments fail more.
- **Blast radius** — `blastRadius` tells you, per domain, whether a config break takes out `email`, `web`, or `identity`.

#### M\&A due-diligence mode

Set `analysisMode: "mna"` to point this at a target company's domain portfolio. The run `summary` adds an `acquisitionReadiness` block — a graded composite of email/DNS posture, operational fragility, infrastructure complexity, and vendor sprawl — that a consultant drops straight into a due-diligence report.

```json
{ "domains": ["targetco.com", "targetco.net", "mail.targetco.com"], "analysisMode": "mna", "expandSpfIncludes": true }
```

***

### Use in Dify

Drop this actor into [Dify](https://docs.apify.com/platform/integrations/dify) workflows via the Apify plugin's Run Actor node. Every domain returns scored, classified and recommended as structured JSON — `fix-now` / `review` / `monitor` / `ok` plus the `riskLevel` and `spoofable` flags your downstream if/else node branches on. A raw DNS tool pointed at the same domain returns record strings you still have to interpret; this returns the decision.

- **Actor ID:** `ryanclinton/dns-record-lookup`
- **Sample input** (deliverability audit, surface only the domains that need work):

```json
{
    "domains": ["example.com", "shop.example.org", "mail.example.net"],
    "analysisMode": "deliverability",
    "maxPostureScore": 70,
    "outputProfile": "minimal"
}
```

- **Branching example** — a Dify if/else node routes on the decision enum:
  - `recommendedAction == "fix-now"` → open a ticket / page the on-call.
  - `emailAuth.spoofable == true` → hold the campaign for that domain.
  - `recommendedAction == "ok"` → continue.
- **Monitoring mode** — add a `watchlistName` and the `monitoring.driftSeverity` enum lets a scheduled Dify flow alert only on `critical` drift (a DMARC downgrade) and ignore the rest.

The `recommendedActions[]` array on `emailAuth` is usable verbatim — concrete remediation steps ("Advance DMARC from p=none to p=quarantine, then p=reject"), no LLM rewriting needed.

***

### Combine with other Apify actors

Each record carries a `nextActions[]` array pointing at the right sibling to run next:

| Actor | Why pair it |
|-------|-------------|
| [WHOIS Domain Lookup](https://apify.com/ryanclinton/whois-domain-lookup) | Registration, registrar, expiry and ownership for the same domains. |
| [SSL Certificate Search](https://apify.com/ryanclinton/crt-sh-search) | Subdomain enumeration and look-alike / typosquat detection via Certificate Transparency. |
| [IP Geolocation Lookup](https://apify.com/ryanclinton/ip-geolocation-lookup) | Geolocate and classify the resolved A/AAAA addresses and mail servers. |
| [Website Tech Stack Detector](https://apify.com/ryanclinton/website-tech-stack-detector) | Cross-reference DNS with the technologies a site runs for a full infrastructure profile. |

***

### API

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("ryanclinton/dns-record-lookup").call(run_input={
    "domains": ["example.com", "shop.example.org"],
    "analysisMode": "deliverability",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item.get("recordType") == "domain":
        print(f'{item["domain"]}: {item["postureGrade"]} — {item["recommendedAction"]} — {item["whyFlagged"]}')
```

```javascript
import { ApifyClient } from "apify-client";

const client = new ApifyClient({ token: "YOUR_API_TOKEN" });
const run = await client.actor("ryanclinton/dns-record-lookup").call({
    domains: ["example.com", "shop.example.org"],
    analysisMode: "deliverability",
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const it of items.filter((r) => r.recordType === "domain")) {
    console.log(`${it.domain}: ${it.postureGrade} — ${it.recommendedAction}${it.emailAuth?.spoofable ? " — SPOOFABLE" : ""}`);
}
```

***

### What this does NOT do

- **It does not probe live SMTP / STARTTLS.** TLS-grade and opportunistic-TLS detection require live SMTP connections to each mail server on port 25 — a different capability from DNS resolution, and one that cloud egress commonly blocks. The DNS-native alternative (DANE / TLSA records) is also out of reach: Node's resolver exposes no TLSA record type. MTA-STS and TLS-RPT cover the DNS-resolvable side of transport security.
- **It does not hunt typosquats / look-alike domains.** The domains you pass in are the subjects being audited, not candidates to compare against a brand. For typosquat and look-alike discovery, use [SSL Certificate Search](https://apify.com/ryanclinton/crt-sh-search), which scans Certificate Transparency logs.
- **It does not expose TTLs.** Node's `dns.promises` API does not return TTL values.
- **Compliance mapping covers the DNS-checkable portion only.** The Google / Microsoft bulk-sender pass/fail reflects SPF + DKIM + DMARC presence. It does not verify spam-rate, one-click-unsubscribe, or message formatting, which are not visible in DNS.
- **It does not exhaustively enumerate DKIM selectors.** It probes common selectors; domains using a custom selector may report `dkim.present: false` even when DKIM is configured.
- **DKIM presence ≠ valid signing.** It confirms a selector record exists, not that mail is correctly signed end to end.
- **Results are resolver-dependent and point-in-time.** Resolution reflects the nameservers Apify's infrastructure uses, at the `lookupTimestamp` recorded.

***

### FAQ

**Is my domain spoofable?** The `emailAuth.spoofable` boolean answers it directly, with `spoofableReason` explaining why. A domain is spoofable when DMARC is not enforced (`p=reject`/`p=quarantine` at 100%) or SPF does not hard/soft-fail unauthorised senders.

**Why does the actor say my SPF "exceeds the limit"?** RFC 7208 caps SPF at 10 DNS lookups. Over the cap, receivers return a `PermError` and SPF silently fails — mail that should pass starts failing. Turn on `expandSpfIncludes` to count the real include-tree depth instead of the static lower bound.

**What does `recommendedAction` mean?** It is the single field to branch on: `fix-now` (critical risk), `review` (high), `monitor` (medium), `ok` (low/none). It is derived deterministically from the risk flags.

**Do I need an API key?** No. It uses Node's native DNS resolver — no third-party accounts, no external rate limits.

**What happens to a domain that does not exist?** It comes back with `resolved: false` and a clear message, is excluded from the posture statistics, and is not billed.

**Can I get just the decision fields?** Set `outputProfile: "minimal"` — you get the score, grade, `spoofable`, `recommendedAction` and the primary identifiers, ideal for Zapier / Make / n8n rules.

**Can I monitor for DNS changes?** Yes — set a `watchlistName` and schedule the run. Each record then carries a `monitoring` block flagging DMARC downgrades, MX / mail-provider changes, and more, with a `driftSeverity` for alert routing.

# Actor input Schema

## `domains` (type: `array`):

List of domains to analyse (e.g. google.com, github.com). Protocol, path, port and www. are stripped automatically; duplicates are removed. Domains that do not resolve are reported but not billed.

## `recordTypes` (type: `array`):

Which DNS record types to query. Leave empty for all types.

## `checkEmailSecurity` (type: `boolean`):

Check SPF, DMARC, and DKIM records for email deliverability and security analysis.

## `timeout` (type: `integer`):

Timeout per DNS query in milliseconds.

## `analysisMode` (type: `string`):

Shifts the emphasis of the posture score (the same email-auth + DNS-resilience signals, reweighted). 'deliverability' weights SPF/DMARC/DKIM heaviest; 'security' gives more weight to DNS resilience and takeover risk; 'monitoring' is tuned for scheduled re-runs; 'mna' (M\&A due diligence) weights infrastructure hygiene and emits an acquisition-readiness summary; 'balanced' is the default blend.

## `outputProfile` (type: `string`):

How much detail per record. 'minimal' = decision fields only (score, grade, spoofable, action) for Zapier/Make rules; 'standard' = decision + key email-auth fields + raw records; 'full' = every diagnostic sub-block.

## `expandSpfIncludes` (type: `boolean`):

Resolve the full SPF include chain to count the real number of DNS lookups (RFC 7208 caps these at 10; over the cap is a PermError that silently breaks SPF) and map the sending vendors. Off by default because it performs extra DNS queries; when off, a static lower-bound count is used.

## `checkDnssec` (type: `boolean`):

Query DNSSEC state via Cloudflare DNS-over-HTTPS (no API key) — reports whether the zone is signed and whether the answer is DNSSEC-validated. Adds one lightweight HTTPS request per domain; turn off to skip.

## `watchlistName` (type: `string`):

Set a name to persist this run's DNS / email-auth fingerprint and diff it on the next run with the same name. Each record then carries a 'monitoring' block flagging DMARC downgrades, MX / mail-provider changes, SPF rewrites and DKIM removals. Leave blank for a one-off scan.

## `maxPostureScore` (type: `integer`):

Only output (and only bill for) domains whose posture score is at or below this threshold — i.e. surface only the domains that need attention. Leave at 100 to return every domain.

## Actor input object example

```json
{
  "domains": [
    "google.com",
    "github.com"
  ],
  "recordTypes": [
    "A",
    "AAAA",
    "MX",
    "NS",
    "TXT",
    "CNAME",
    "SOA"
  ],
  "checkEmailSecurity": true,
  "timeout": 5000,
  "analysisMode": "balanced",
  "outputProfile": "standard",
  "expandSpfIncludes": false,
  "checkDnssec": true,
  "maxPostureScore": 100
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "domains": [
        "google.com",
        "github.com"
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "MX",
        "NS",
        "TXT",
        "CNAME",
        "SOA"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ryanclinton/dns-record-lookup").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "domains": [
        "google.com",
        "github.com",
    ],
    "recordTypes": [
        "A",
        "AAAA",
        "MX",
        "NS",
        "TXT",
        "CNAME",
        "SOA",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("ryanclinton/dns-record-lookup").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domains": [
    "google.com",
    "github.com"
  ],
  "recordTypes": [
    "A",
    "AAAA",
    "MX",
    "NS",
    "TXT",
    "CNAME",
    "SOA"
  ]
}' |
apify call ryanclinton/dns-record-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=ryanclinton/dns-record-lookup",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/k1EsC3laNunPVzudh/builds/j8U5WW3L4cptjCS01/openapi.json
