File size: 4,046 Bytes
5db9737
 
 
 
 
 
 
 
 
 
 
 
 
c4729ea
 
 
 
 
 
 
 
 
 
 
 
5db9737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd07add
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5db9737
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
    )