Skip to main content

disposable_emails/
lib.rs

1//! Disposable / temporary email domain detection.
2//!
3//! Tells you whether an email address (or just its domain) belongs to a
4//! disposable / temp-mail provider — the throwaway inboxes used to farm free
5//! trials, dodge per-account limits, and spam sign-up flows.
6//!
7//! ```
8//! use disposable_emails::{is_disposable_email, is_disposable_domain};
9//!
10//! assert!(is_disposable_email("someone@mailinator.com"));
11//! assert!(is_disposable_domain("guerrillamail.com"));
12//! assert!(!is_disposable_email("jeff@gmail.com"));
13//! ```
14//!
15//! # Why it's cheap
16//!
17//! The ~72,000-domain blocklist is compiled into a **sorted
18//! `&'static [&'static str]`** by the build script. That means:
19//!
20//! - **zero heap allocation** — the list lives in the binary's read-only
21//!   segment; there is no `HashSet` to build,
22//! - **zero startup cost** — nothing is parsed or loaded at runtime,
23//! - lookups are an `O(log n)` binary search (~17 comparisons).
24//!
25//! The only allocation per call is a small lowercase copy of the *input*
26//! domain (tens of bytes).
27//!
28//! # The list
29//!
30//! `domains.txt` is a vendored copy of the community-maintained
31//! [`disposable`](https://github.com/disposable/disposable-email-domains)
32//! aggregate. Refresh it by replacing that file and rebuilding.
33//!
34//! # Licence
35//!
36//! Dual-licensed under Apache-2.0 OR MIT.
37
38#![forbid(unsafe_code)]
39
40include!(concat!(env!("OUT_DIR"), "/domains_generated.rs"));
41
42/// The number of disposable domains in the embedded blocklist.
43#[inline]
44pub fn domain_count() -> usize {
45    DOMAINS.len()
46}
47
48/// Returns `true` if `domain` is a known disposable / temp-mail domain.
49///
50/// Input is matched case-insensitively; a single trailing `.` (a
51/// fully-qualified domain) is tolerated. Subdomains are **not** walked — pass
52/// the registrable domain.
53///
54/// ```
55/// assert!(disposable_emails::is_disposable_domain("Mailinator.com"));
56/// assert!(!disposable_emails::is_disposable_domain("spider.cloud"));
57/// ```
58pub fn is_disposable_domain(domain: &str) -> bool {
59    let d = domain.trim().trim_end_matches('.').to_ascii_lowercase();
60    if d.is_empty() {
61        return false;
62    }
63    DOMAINS.binary_search(&d.as_str()).is_ok()
64}
65
66/// Returns `true` if the email address's domain is disposable.
67///
68/// Malformed input (no `@`, empty domain) returns `false` rather than erroring
69/// — callers that need strict validation should check the address shape first.
70///
71/// ```
72/// assert!(disposable_emails::is_disposable_email("x@10minutemail.com"));
73/// assert!(!disposable_emails::is_disposable_email("not-an-email"));
74/// ```
75pub fn is_disposable_email(email: &str) -> bool {
76    match email.rsplit_once('@') {
77        Some((_, domain)) if !domain.is_empty() => is_disposable_domain(domain),
78        _ => false,
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn list_is_substantial_and_sorted() {
88        assert!(domain_count() > 50_000, "blocklist looks too small");
89        assert!(
90            DOMAINS.windows(2).all(|w| w[0] < w[1]),
91            "DOMAINS must be sorted + deduped for binary_search"
92        );
93    }
94
95    #[test]
96    fn flags_known_disposable() {
97        for d in ["mailinator.com", "guerrillamail.com", "10minutemail.com"] {
98            assert!(is_disposable_domain(d), "{d} should be disposable");
99        }
100    }
101
102    #[test]
103    fn allows_real_domains() {
104        for d in ["gmail.com", "spider.cloud", "gottem.dev", "outlook.com"] {
105            assert!(!is_disposable_domain(d), "{d} should not be disposable");
106        }
107    }
108
109    #[test]
110    fn case_and_trailing_dot_insensitive() {
111        assert!(is_disposable_domain("MAILINATOR.COM"));
112        assert!(is_disposable_domain("mailinator.com."));
113    }
114
115    #[test]
116    fn email_form() {
117        assert!(is_disposable_email("a@mailinator.com"));
118        assert!(is_disposable_email("weird+tag@guerrillamail.com"));
119        assert!(!is_disposable_email("jeff@gmail.com"));
120        assert!(!is_disposable_email("garbage"));
121        assert!(!is_disposable_email("trailing@"));
122    }
123}