modul-x / app /validator /disposable.py
sitcod3's picture
Upload folder using huggingface_hub
c4729ea verified
Raw History Blame Contribute Delete
4.05 kB
import disposable_email_domains
from ..models import DisposableResult
# Extended free provider list
FREE_PROVIDERS = {
"gmail.com", "yahoo.com", "hotmail.com", "outlook.com",
"aol.com", "icloud.com", "protonmail.com", "zoho.com",
"mail.com", "yandex.com", "yandex.ru", "gmx.com",
"gmx.net", "live.com", "msn.com", "me.com",
"mac.com", "googlemail.com", "yahoo.co.uk", "yahoo.fr",
"yahoo.de", "yahoo.it", "yahoo.es", "yahoo.com.br",
"hotmail.fr", "hotmail.de", "hotmail.it", "hotmail.es",
"hotmail.co.uk", "fastmail.com", "tutanota.com",
"pm.me", "proton.me", "hey.com", "duck.com",
# ISP mailbox domains: legitimate consumer mailboxes handled by the
# deterministic free-provider path, never by the ML advisory (the model has
# no training signal for them and flagged sbcglobal.net as disposable).
"sbcglobal.net", "att.net", "comcast.net", "verizon.net",
"bellsouth.net", "cox.net", "charter.net", "earthlink.net",
"roadrunner.com", "rr.com", "shaw.ca", "rogers.com",
"btinternet.com", "sky.com", "virginmedia.com", "orange.fr",
"wanadoo.fr", "free.fr", "laposte.net", "web.de",
"t-online.de", "arcor.de", "libero.it", "virgilio.it",
"terra.com.br", "uol.com.br", "bol.com.br", "seznam.cz",
"naver.com", "daum.net", "hanmail.net", "rediffmail.com",
}
# Role-based email prefixes
ROLE_BASED_PREFIXES = {
"admin", "info", "support", "help", "sales", "contact",
"hello", "hi", "noreply", "no-reply", "noreply",
"donotreply", "do-not-reply", "webmaster", "postmaster",
"abuse", "security", "billing", "accounts", "hr",
"humanresources", "marketing", "team", "staff", "office",
"enquiries", "enquiry", "inquiry", "feedback", "service",
"services", "care", "press", "media", "privacy",
"legal", "compliance", "jobs", "careers", "recruitment",
"newsletter", "notifications", "alerts", "updates",
"root", "sys", "sysadmin", "system", "ops", "devops",
"dev", "api", "mail", "email", "bounce", "mailer"
}
# Nice names for common free providers
PROVIDER_MAP = {
"gmail.com": "Gmail (Google)",
"yahoo.com": "Yahoo Mail",
"hotmail.com": "Hotmail (Microsoft)",
"outlook.com": "Outlook (Microsoft)",
"icloud.com": "iCloud (Apple)",
"protonmail.com": "ProtonMail",
"proton.me": "Proton Mail",
"zoho.com": "Zoho Mail",
}
def domain_in_blocklist(domain: str) -> bool:
"""Exact-match against the disposable blocklist (the deterministic truth)."""
return domain in disposable_email_domains.blocklist
def domain_is_known(domain: str) -> bool:
"""True when a curated list already answers for this domain.
The ML signal runs ONLY on domains outside every curated list - those are
the ones a static blocklist cannot classify, and the only place a model
adds value instead of risk.
"""
return domain in disposable_email_domains.blocklist or domain in FREE_PROVIDERS
def check_disposable(email: str, domain: str) -> DisposableResult:
"""
Layer 4: Comprehensive disposable, free provider,
and role-based detection.
"""
local_part = email.split('@')[0].lower()
# Disposable domain check (100K+ domains database)
is_disposable = domain in disposable_email_domains.blocklist
# Free provider check
is_free = domain in FREE_PROVIDERS
# Role-based check (exact match + prefix match)
is_role = (
local_part in ROLE_BASED_PREFIXES or
any(local_part.startswith(prefix)
for prefix in ROLE_BASED_PREFIXES)
)
# Determine provider name
provider_name = None
if is_free:
provider_name = PROVIDER_MAP.get(domain, "Free Email Provider")
elif is_disposable:
provider_name = "Disposable/Temporary Email"
return DisposableResult(
is_disposable=is_disposable,
is_free_provider=is_free,
is_role_based=is_role,
provider_name=provider_name
)