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=...
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', ...}
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"]}'
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)
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.
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.