Skip to content

Instantly share code, notes, and snippets.

@Igloczek
Last active June 1, 2026 17:05
Show Gist options
  • Select an option

  • Save Igloczek/7bfc459109f4a01f7e046b591d2a842a to your computer and use it in GitHub Desktop.

Select an option

Save Igloczek/7bfc459109f4a01f7e046b591d2a842a to your computer and use it in GitHub Desktop.
Simple way to filter out most of the disposable / temporary / disposable / burner / fake / dead emails
import dns from "node:dns/promises"
import axios from "axios"
import { TTLCache } from "@isaacs/ttlcache"
const domainsCache = new TTLCache<string, boolean>({
ttl: 1000 * 60 * 60 * 1,
})
const dnsCache = new TTLCache<string, boolean>({
ttl: 1000 * 60 * 60 * 1,
})
const resolver = new dns.Resolver({ timeout: 5000, tries: 3 })
resolver.setServers(["1.1.1.1", "8.8.8.8"])
const TRANSIENT_ERRORS = new Set([
"ETIMEOUT",
"ESERVFAIL",
"ECONNREFUSED",
"EREFUSED",
])
const KNOWN_PROVIDERS = new Set([
"gmail.com",
"googlemail.com",
"yahoo.com",
"yahoo.co.uk",
"hotmail.com",
"hotmail.co.uk",
"outlook.com",
"icloud.com",
"me.com",
"mac.com",
"live.com",
"msn.com",
"proton.me",
"protonmail.com",
"hey.com",
"fastmail.com",
"aol.com",
])
interface ListConfig {
url: string
type: "text" | "json"
}
const lists: ListConfig[] = [
{
url: "https://deviceandbrowserinfo.com/api/emails/disposable",
type: "json",
},
{
url: "https://raw.githubusercontent.com/Igloczek/burner-email-providers/master/emails.txt",
type: "text",
},
{
url: "https://raw.githubusercontent.com/wesbos/burner-email-providers/master/emails.txt",
type: "text",
},
{
url: "https://raw.githubusercontent.com/7c/fakefilter/main/txt/data.txt",
type: "text",
},
{
url: "https://raw.githubusercontent.com/unkn0w/disposable-email-domain-list/main/domains.txt",
type: "text",
},
{
url: "https://raw.githubusercontent.com/disposable/disposable-email-domains/master/domains_strict.txt",
type: "text",
},
{
url: "https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf",
type: "text",
},
]
async function loadDomains() {
await Promise.allSettled(
lists.map(async (list) => {
const response = await axios.get(list.url)
if (list.type === "json") {
// Handle JSON response - assuming it's an array of domains
const jsonData = Array.isArray(response.data) ? response.data : []
jsonData.forEach((domain) => {
if (typeof domain === "string" && domain.trim() !== "") {
domainsCache.set(domain.trim(), true)
}
})
} else {
// Handle text response
response.data.split("\n").forEach((item: string) => {
const line = item.trim()
if (line !== "" && !line.startsWith("#")) {
domainsCache.set(line, true)
}
})
}
}),
)
}
// warm up the cache
await loadDomains()
// automatically refresh lists every hour in the background
setInterval(() => {
loadDomains().catch(console.error)
}, 1000 * 60 * 60 * 1)
/**
* Check if a domain can receive email, per RFC 5321:
* 1. Try MX records first
* 2. If no MX, fall back to A/AAAA records (implicit MX)
* 3. Only mark as dead if the domain has none of the above
*/
async function checkDnsRecords(domain: string): Promise<boolean> {
// Step 1: Check MX records
try {
const mx = await resolver.resolveMx(domain)
if (mx && mx.length > 0) return true
} catch (err) {
const error = err as { code?: string }
if (error.code && TRANSIENT_ERRORS.has(error.code)) return true
if (error.code === "ENOTFOUND") return false
// ENODATA = domain exists but no MX records, fall through to A/AAAA check
}
// Step 2: RFC 5321 fallback — check A/AAAA records (implicit MX)
try {
const a = await resolver.resolve4(domain)
if (a && a.length > 0) return true
} catch {
// ignore
}
try {
const aaaa = await resolver.resolve6(domain)
if (aaaa && aaaa.length > 0) return true
} catch {
// ignore
}
return false
}
async function hasEmailDnsRecords(domain: string): Promise<boolean> {
const cached = dnsCache.get(domain)
if (cached !== undefined) {
return cached
}
const result = await checkDnsRecords(domain)
dnsCache.set(domain, result)
return result
}
export async function verifyEmail(email: string) {
const parts = email.split("@")
if (parts.length !== 2) {
return false
}
const domain = parts[1]
if (KNOWN_PROVIDERS.has(domain)) {
return true
}
const isDisposable = domainsCache.get(domain) === true
if (isDisposable) {
return false
}
const hasDnsRecords = await hasEmailDnsRecords(domain)
if (!hasDnsRecords) {
return false
}
return true
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment