DEV Community

Mohamed T.
Mohamed T.

Posted on AI-assisted

Your prospects' DNS records tell you which SaaS they pay for

If you sell to companies, knowing their tech stack is the best qualifier there is. A Shopify app wants Shopify stores. A HubSpot agency wants HubSpot users. A security vendor wants companies that just moved to Microsoft 365.

Most tech detectors (Wappalyzer, BuiltWith-style browser extensions) look at the web page: script tags, headers, cookies. That's fine for the front-end. But most of what a company pays for never appears on its homepage: the CRM, email, helpdesk, payment processor, e-signature and security tools.

A lot of it shows up in DNS, though.

Why DNS leaks the back-office stack

When a company signs up for a SaaS tool, the tool usually asks the company to prove it owns its domain. The standard way is to add a TXT record:

google-site-verification=...
atlassian-domain-verification=...
docusign=...
stripe-verification=...
Enter fullscreen mode Exit fullscreen mode

Three other record types add to the picture:

  • MX records say who handles email (Google Workspace, Microsoft 365, Proofpoint, Mimecast...).
  • SPF (v=spf1 include:...) lists every service allowed to send email for the domain: Mailchimp, SendGrid, Postmark, HubSpot, Zendesk...
  • CNAME records on subdomains (help., status., go.) often point to Zendesk, Statuspage, HubSpot...

None of this is secret. It's public by design, because mail servers and SaaS vendors must be able to read it.

Read it yourself in 30 lines

DNS-over-HTTPS makes this easy from any language. No DNS library is needed:

import httpx

DOH = "https://cloudflare-dns.com/dns-query"

def records(name: str, rtype: str) -> list[str]:
    r = httpx.get(DOH, params={"name": name, "type": rtype},
                  headers={"accept": "application/dns-json"}, timeout=10)
    return [a["data"].strip('"') for a in r.json().get("Answer", [])]

SIGNATURES = {
    "google-site-verification": "Google Search Console",
    "atlassian-domain-verification": "Atlassian",
    "docusign": "DocuSign",
    "stripe-verification": "Stripe",
    "hubspot": "HubSpot",
    "_spf.google.com": "Google Workspace (sends mail)",
    "spf.protection.outlook.com": "Microsoft 365 (sends mail)",
    "mailgun.org": "Mailgun",
    "sendgrid.net": "SendGrid",
    "spf.mtasv.net": "Postmark",
}

def saas_from_dns(domain: str) -> set[str]:
    txt = records(domain, "TXT")
    mx = records(domain, "MX")
    found = {name for rec in txt for sig, name in SIGNATURES.items() if sig in rec.lower()}
    if any("google.com" in m for m in mx):
        found.add("Google Workspace (mailboxes)")
    if any("outlook.com" in m for m in mx):
        found.add("Microsoft 365 (mailboxes)")
    return found

print(saas_from_dns("linear.app"))
# {'Google Workspace (mailboxes)', 'HubSpot', 'Stripe', 'Postmark', 'DocuSign', ...}
Enter fullscreen mode Exit fullscreen mode

That's the whole trick. The hard part is the signature list: there are hundreds of verification formats, and vendors change them.

What it looks like on real companies

I ran three well-known domains through a detector that combines page fingerprints with DNS (results from September 2026):

Domain Seen on the page Seen only in DNS
linear.app React, Sanity, Algolia, Google Cloud CDN Google Workspace, HubSpot, Stripe, Slack, Notion, Postmark, Amazon SES, MongoDB Atlas, DocuSign, Zoom, Cursor, OpenAI, Anthropic
gymshark.com Shopify, PayPal, Apple Pay, Cloudflare Microsoft 365, Proofpoint, Atlassian, Jamf, OneTrust, HackerOne, Mixpanel, Adobe, Meta Business Suite, OpenAI, Anthropic
notion.com Next.js, Vercel, Contentful, Algolia, hCaptcha Cloudflare DNS, Postman, Zoom

For Linear, 19 of the 33 technologies detected came from DNS alone, including almost every business tool. A page-only detector would call Gymshark "a Shopify store". DNS shows a Microsoft 365 + Proofpoint + Jamf company with a bug-bounty program, which is a very different prospect for a security or IT vendor.

Caveats (read these before you build a lead list)

  • A verification record means "has an account", not "is a happy paying customer". Companies rarely clean up old TXT records. Treat DNS hits as strong signals, not proof.
  • Some tools don't verify domains. No signal doesn't mean no usage.
  • Only query what's public, and be polite. DNS-over-HTTPS resolvers have rate limits, so batch and back off.
  • This is company data, not personal data. Keep it that way: don't pair it with scraped employee emails.

Doing it at scale

For a handful of domains, the script above is enough. For a list of 5,000 prospects, you need three more things:

  • a maintained signature database;
  • retries on DNS and HTTP errors;
  • the page-level detection as well.

That's why I built Tech Stack & SaaS Detector on Apify:

  • it merges the open Wappalyzer-format fingerprint database (7,600+ technologies) with DNS rules;
  • it respects robots.txt;
  • it returns one JSON row per domain, with how each technology was detected (detectedVia: ["dns"]).

It costs $0.004 per domain, and unreachable domains are free.

curl -X POST "https://api.apify.com/v2/acts/moha-tah~tech-stack-detector/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
  -d '{"domains": ["linear.app", "gymshark.com"], "matchTechnologies": ["HubSpot", "Shopify"]}'
Enter fullscreen mode Exit fullscreen mode

matchTechnologies adds a matchesAny / matchesAll flag to each row, so filtering a prospect list is one line.

If you'd rather not write code:

  • n8n: a ready-made workflow reads domains from a Google Sheet, writes the stack back and posts matching leads to Slack (workflow JSON).
  • AI agents: the same detector is available as an MCP tool, so Claude or Cursor can answer "which of these 50 domains use HubSpot?" directly (setup).

Takeaway

The homepage shows what a company builds with. DNS shows what it buys. If you qualify leads by stack, read both.

Top comments (2)

Collapse
 
mohith_kumar_05846f3211f3 profile image
Mohith kumar •

Clever signal. It pairs well with inbound: when a lead fills a form, checking MX/TXT records instantly tells sales which stack they're on before the first call. Cheap enrichment with zero extra questions for the lead.

Collapse
 
elijahbrown profile image
Elijah Brown •

Nice approach. Two things I'd handle when reading MX and SPF: a domain with a Null MX (a single MX of 0 and ".") has said it takes no mail at all, which is a signal in itself, and SPF includes are often leftovers from tools a company stopped paying for, so an include on its own is weak evidence of a current tool.