temp-email-filter 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ # Contributing
2
+
3
+ Thanks for helping improve Temp Email Filter.
4
+
5
+ ## Local Setup
6
+
7
+ ```bash
8
+ python -m venv .venv
9
+ source .venv/bin/activate
10
+ pip install -e ".[dev]"
11
+ pytest
12
+ ```
13
+
14
+ ## Guidelines
15
+
16
+ - Keep changes focused and small.
17
+ - Add tests for behavior changes.
18
+ - Do not log raw email addresses or other sensitive user input.
19
+ - Validate external input before DNS or cache operations.
20
+ - Avoid adding dependencies unless they are necessary.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Temp Email Filter contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,21 @@
1
+ include README.md
2
+ include LICENSE
3
+ include SECURITY.md
4
+ include CONTRIBUTING.md
5
+ recursive-include docs *.md
6
+ recursive-include src py.typed
7
+
8
+ prune .github
9
+ prune .pytest_cache
10
+ prune .ruff_cache
11
+ prune .venv
12
+ prune build
13
+ prune dist
14
+ prune htmlcov
15
+ prune tests
16
+
17
+ global-exclude __pycache__
18
+ global-exclude *.py[cod]
19
+ global-exclude .DS_Store
20
+ global-exclude .env
21
+ global-exclude .env.*
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: temp-email-filter
3
+ Version: 0.1.1
4
+ Summary: Detect disposable email addresses with domain, MX, pattern, and RBL checks.
5
+ Author: Temp Email Filter contributors
6
+ License-Expression: MIT
7
+ Keywords: email,disposable-email,validation,dns,temporary-email
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Communications :: Email
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: diskcache>=5.6
20
+ Requires-Dist: dnspython>=2.6
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=8.0; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # Temp Email Filter
26
+
27
+ Temp Email Filter is a Python package for detecting disposable email addresses. It performs comprehensive checks including known disposable domains, trusted provider validation, email patterns, MX record validation, blocked MX hosts, and DNS-based RBL (Real-time Blackhole List) services.
28
+
29
+ ## Features
30
+
31
+ - **Trusted provider check**: Immediately validates emails from known legitimate providers (Gmail, Yahoo, etc.)
32
+ - **Disposable domain detection**: Checks against known temporary email services
33
+ - **Pattern analysis**: Identifies suspicious email patterns (excludes legitimate + aliases)
34
+ - **MX record validation**: Verifies domain has valid mail servers
35
+ - **RBL checking**: Queries reputation databases using proper IP-based lookups
36
+ - **Intelligent caching**: Caches both positive and negative results for performance
37
+ - **DNS timeouts**: Prevents hanging on slow DNS queries
38
+ - **Input validation**: Normalizes and validates email format before processing
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install temp-email-filter
44
+ ```
45
+
46
+ For local development from a clone:
47
+
48
+ ```bash
49
+ git clone <your-repo-url>
50
+ cd temp_email_filter
51
+ python -m venv .venv
52
+ source .venv/bin/activate
53
+ pip install -e ".[dev]"
54
+ ```
55
+
56
+ ## Basic Usage
57
+
58
+ ```python
59
+ from temp_email_filter import check_email
60
+
61
+ result = check_email("test@gmail.com")
62
+ print(f"Email: {result.email}")
63
+ print(f"Is disposable: {result.is_disposable}")
64
+ print(f"Reason: {result.reason}")
65
+
66
+ if result.is_disposable:
67
+ print("This email should be rejected")
68
+ else:
69
+ print("This email is acceptable")
70
+ ```
71
+
72
+ If you only need the original tuple style:
73
+
74
+ ```python
75
+ from temp_email_filter import is_disposable_email
76
+
77
+ is_disposable, reason = is_disposable_email("test@gmail.com")
78
+ ```
79
+
80
+ ## Django Example
81
+
82
+ ```python
83
+ from django.core.exceptions import ValidationError
84
+ from temp_email_filter import check_email
85
+
86
+
87
+ def validate_non_disposable_email(value):
88
+ result = check_email(value)
89
+ if result.is_disposable:
90
+ raise ValidationError(result.reason)
91
+ ```
92
+
93
+ Use the validator in a model or serializer field.
94
+
95
+ ## FastAPI Example
96
+
97
+ This package does not start a FastAPI server. Add it to your own FastAPI app:
98
+
99
+ ```python
100
+ from fastapi import FastAPI, HTTPException
101
+ from temp_email_filter import check_email
102
+
103
+ app = FastAPI()
104
+
105
+
106
+ @app.get("/email-checker")
107
+ def email_checker(email: str):
108
+ result = check_email(email)
109
+ if result.reason.startswith("Invalid email"):
110
+ raise HTTPException(status_code=400, detail=result.reason)
111
+
112
+ return {
113
+ "email": result.email,
114
+ "is_disposable": result.is_disposable,
115
+ "reason": result.reason,
116
+ }
117
+ ```
118
+
119
+ ## Running As A Service
120
+
121
+ This project is package-only. To run it as a service, install it inside your own Django, FastAPI, Flask, worker, or microservice project and call `check_email()` from your route, form validation, serializer, or background job.
122
+
123
+ ## Configuration
124
+
125
+ The cache directory defaults to `.email_cache`. Override it with:
126
+
127
+ ```bash
128
+ export TEMP_EMAIL_FILTER_CACHE_DIR=/tmp/temp-email-filter-cache
129
+ ```
130
+
131
+ ## Recent Improvements (v0.1.1)
132
+
133
+ - **Fixed RBL checking**: Now properly queries MX record IPs instead of domains
134
+ - **Improved pattern matching**: Removed false positive for legitimate + aliases (john+work@gmail.com)
135
+ - **Enhanced caching**: Now caches both disposable and legitimate results
136
+ - **Better logic flow**: Trusted providers checked first, bypassing expensive DNS operations
137
+ - **DNS timeouts**: Added configurable timeouts to prevent hanging queries
138
+ - **Transient error handling**: Avoids caching temporary DNS failures
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ pip install -e ".[dev]"
144
+ pytest
145
+ ```
146
+
147
+ ## Publishing
148
+
149
+ Maintainer release steps are documented in [`docs/PYPI.md`](docs/PYPI.md).
150
+
151
+ ## License
152
+
153
+ MIT
@@ -0,0 +1,129 @@
1
+ # Temp Email Filter
2
+
3
+ Temp Email Filter is a Python package for detecting disposable email addresses. It performs comprehensive checks including known disposable domains, trusted provider validation, email patterns, MX record validation, blocked MX hosts, and DNS-based RBL (Real-time Blackhole List) services.
4
+
5
+ ## Features
6
+
7
+ - **Trusted provider check**: Immediately validates emails from known legitimate providers (Gmail, Yahoo, etc.)
8
+ - **Disposable domain detection**: Checks against known temporary email services
9
+ - **Pattern analysis**: Identifies suspicious email patterns (excludes legitimate + aliases)
10
+ - **MX record validation**: Verifies domain has valid mail servers
11
+ - **RBL checking**: Queries reputation databases using proper IP-based lookups
12
+ - **Intelligent caching**: Caches both positive and negative results for performance
13
+ - **DNS timeouts**: Prevents hanging on slow DNS queries
14
+ - **Input validation**: Normalizes and validates email format before processing
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install temp-email-filter
20
+ ```
21
+
22
+ For local development from a clone:
23
+
24
+ ```bash
25
+ git clone <your-repo-url>
26
+ cd temp_email_filter
27
+ python -m venv .venv
28
+ source .venv/bin/activate
29
+ pip install -e ".[dev]"
30
+ ```
31
+
32
+ ## Basic Usage
33
+
34
+ ```python
35
+ from temp_email_filter import check_email
36
+
37
+ result = check_email("test@gmail.com")
38
+ print(f"Email: {result.email}")
39
+ print(f"Is disposable: {result.is_disposable}")
40
+ print(f"Reason: {result.reason}")
41
+
42
+ if result.is_disposable:
43
+ print("This email should be rejected")
44
+ else:
45
+ print("This email is acceptable")
46
+ ```
47
+
48
+ If you only need the original tuple style:
49
+
50
+ ```python
51
+ from temp_email_filter import is_disposable_email
52
+
53
+ is_disposable, reason = is_disposable_email("test@gmail.com")
54
+ ```
55
+
56
+ ## Django Example
57
+
58
+ ```python
59
+ from django.core.exceptions import ValidationError
60
+ from temp_email_filter import check_email
61
+
62
+
63
+ def validate_non_disposable_email(value):
64
+ result = check_email(value)
65
+ if result.is_disposable:
66
+ raise ValidationError(result.reason)
67
+ ```
68
+
69
+ Use the validator in a model or serializer field.
70
+
71
+ ## FastAPI Example
72
+
73
+ This package does not start a FastAPI server. Add it to your own FastAPI app:
74
+
75
+ ```python
76
+ from fastapi import FastAPI, HTTPException
77
+ from temp_email_filter import check_email
78
+
79
+ app = FastAPI()
80
+
81
+
82
+ @app.get("/email-checker")
83
+ def email_checker(email: str):
84
+ result = check_email(email)
85
+ if result.reason.startswith("Invalid email"):
86
+ raise HTTPException(status_code=400, detail=result.reason)
87
+
88
+ return {
89
+ "email": result.email,
90
+ "is_disposable": result.is_disposable,
91
+ "reason": result.reason,
92
+ }
93
+ ```
94
+
95
+ ## Running As A Service
96
+
97
+ This project is package-only. To run it as a service, install it inside your own Django, FastAPI, Flask, worker, or microservice project and call `check_email()` from your route, form validation, serializer, or background job.
98
+
99
+ ## Configuration
100
+
101
+ The cache directory defaults to `.email_cache`. Override it with:
102
+
103
+ ```bash
104
+ export TEMP_EMAIL_FILTER_CACHE_DIR=/tmp/temp-email-filter-cache
105
+ ```
106
+
107
+ ## Recent Improvements (v0.1.1)
108
+
109
+ - **Fixed RBL checking**: Now properly queries MX record IPs instead of domains
110
+ - **Improved pattern matching**: Removed false positive for legitimate + aliases (john+work@gmail.com)
111
+ - **Enhanced caching**: Now caches both disposable and legitimate results
112
+ - **Better logic flow**: Trusted providers checked first, bypassing expensive DNS operations
113
+ - **DNS timeouts**: Added configurable timeouts to prevent hanging queries
114
+ - **Transient error handling**: Avoids caching temporary DNS failures
115
+
116
+ ## Development
117
+
118
+ ```bash
119
+ pip install -e ".[dev]"
120
+ pytest
121
+ ```
122
+
123
+ ## Publishing
124
+
125
+ Maintainer release steps are documented in [`docs/PYPI.md`](docs/PYPI.md).
126
+
127
+ ## License
128
+
129
+ MIT
@@ -0,0 +1,12 @@
1
+ # Security Policy
2
+
3
+ Please report security issues privately to the project maintainers before public disclosure.
4
+
5
+ When reporting, include:
6
+
7
+ - A clear description of the issue
8
+ - Steps to reproduce
9
+ - Expected impact
10
+ - Any safe proof of concept details
11
+
12
+ Do not include real user email addresses, secrets, or private DNS data in reports.
@@ -0,0 +1,94 @@
1
+ # PyPI Publishing
2
+
3
+ This document is for project maintainers publishing `temp-email-filter`.
4
+
5
+ ## Prerequisites
6
+
7
+ - A PyPI account with a project-scoped API token
8
+ - A TestPyPI account with a project-scoped API token
9
+ - A clean git working tree
10
+ - Passing tests
11
+
12
+ Do not commit PyPI tokens, `.pypirc`, or environment files.
13
+
14
+ ## Prepare The Release
15
+
16
+ Update the package version in `pyproject.toml`:
17
+
18
+ ```toml
19
+ [project]
20
+ version = "0.1.1"
21
+ ```
22
+
23
+ Install release tools:
24
+
25
+ ```bash
26
+ python -m venv .venv
27
+ source .venv/bin/activate
28
+ pip install -U pip build twine
29
+ pip install -e ".[dev]"
30
+ ```
31
+
32
+ Run tests:
33
+
34
+ ```bash
35
+ pytest
36
+ ```
37
+
38
+ Build the package:
39
+
40
+ ```bash
41
+ python -m build
42
+ ```
43
+
44
+ Validate the distribution files:
45
+
46
+ ```bash
47
+ twine check dist/*
48
+ ```
49
+
50
+ ## Publish To TestPyPI
51
+
52
+ Use an API token from TestPyPI:
53
+
54
+ ```bash
55
+ export TWINE_USERNAME=__token__
56
+ export TWINE_PASSWORD=<testpypi-token>
57
+ twine upload --repository testpypi dist/*
58
+ ```
59
+
60
+ Test install from TestPyPI:
61
+
62
+ ```bash
63
+ python -m venv /tmp/temp-email-filter-test
64
+ source /tmp/temp-email-filter-test/bin/activate
65
+ pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ temp-email-filter
66
+ python -c "from temp_email_filter import check_email; print(check_email('test@gmail.com'))"
67
+ ```
68
+
69
+ ## Publish To PyPI
70
+
71
+ After TestPyPI install works, upload to production PyPI:
72
+
73
+ ```bash
74
+ export TWINE_USERNAME=__token__
75
+ export TWINE_PASSWORD=<pypi-token>
76
+ twine upload dist/*
77
+ ```
78
+
79
+ ## Verify The Release
80
+
81
+ Install from PyPI in a fresh environment:
82
+
83
+ ```bash
84
+ python -m venv /tmp/temp-email-filter-release
85
+ source /tmp/temp-email-filter-release/bin/activate
86
+ pip install temp-email-filter
87
+ python -c "from temp_email_filter import check_email; print(check_email('test@gmail.com'))"
88
+ ```
89
+
90
+ ## Common Issues
91
+
92
+ - PyPI versions are immutable. If upload fails after a partial release, bump the version before retrying.
93
+ - Use `rm -rf dist build src/*.egg-info` before rebuilding if old artifacts are present.
94
+ - Use project-scoped tokens instead of account passwords.
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "temp-email-filter"
7
+ version = "0.1.1"
8
+ description = "Detect disposable email addresses with domain, MX, pattern, and RBL checks."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [{ name = "Temp Email Filter contributors" }]
13
+ keywords = ["email", "disposable-email", "validation", "dns", "temporary-email"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Communications :: Email",
23
+ ]
24
+ dependencies = [
25
+ "diskcache>=5.6",
26
+ "dnspython>=2.6",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest>=8.0",
32
+ ]
33
+
34
+ [tool.setuptools.packages.find]
35
+ where = ["src"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
39
+ pythonpath = ["src"]
40
+
41
+ [tool.pyright]
42
+ venvPath = "."
43
+ venv = ".venv"
44
+ pythonVersion = "3.9"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .checker import check_email, is_disposable_email
2
+
3
+ __all__ = ["check_email", "is_disposable_email"]
@@ -0,0 +1,266 @@
1
+ import logging
2
+ import os
3
+ import re
4
+ import socket
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any, Optional, Tuple, cast
8
+
9
+ import diskcache
10
+ import dns.resolver
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Configure DNS timeouts to prevent hanging
15
+ try:
16
+ dns.resolver.default_resolver.timeout = 2
17
+ dns.resolver.default_resolver.lifetime = 5
18
+ except (AttributeError, TypeError):
19
+ # Some environments may not support resolver configuration
20
+ pass
21
+
22
+ DEFAULT_CACHE_DIR = Path(os.getenv("TEMP_EMAIL_FILTER_CACHE_DIR", ".email_cache"))
23
+ cache = diskcache.Cache(str(DEFAULT_CACHE_DIR))
24
+
25
+ KNOWN_NOT_DISPOSABLE_DOMAINS = {
26
+ "gmail.com",
27
+ "yahoo.com",
28
+ "hotmail.com",
29
+ "outlook.com",
30
+ "protonmail.com",
31
+ "aol.com",
32
+ "icloud.com",
33
+ "zoho.com",
34
+ "mail.com",
35
+ "gmx.com",
36
+ }
37
+
38
+ DISPOSABLE_DOMAINS = {
39
+ "tempmail.com",
40
+ "throwawaymail.com",
41
+ "10minutemail.com",
42
+ "guerrillamail.com",
43
+ "mailinator.com",
44
+ "sharklasers.com",
45
+ "meltmail.com",
46
+ "yopmail.com",
47
+ "fakeinbox.com",
48
+ "trashmail.com",
49
+ "mintemail.com",
50
+ "maildrop.cc",
51
+ "getnada.com",
52
+ "dispostable.com",
53
+ "spamgourmet.com",
54
+ "jetable.org",
55
+ "mailnesia.com",
56
+ "spamavert.com",
57
+ "emailondeck.com",
58
+ "mytemp.email",
59
+ "tmail.com",
60
+ "boun.cr",
61
+ "mailcatch.com",
62
+ "temp-mail.org",
63
+ "moakt.com",
64
+ "dropmail.me",
65
+ "burnermail.io",
66
+ "mailinator.net",
67
+ "trashmail.net",
68
+ "20minutemail.com",
69
+ "spambog.com",
70
+ "fake-mail.net",
71
+ "emailtemporario.com.br",
72
+ "fakemailgenerator.com",
73
+ "getairmail.com",
74
+ "tempail.com",
75
+ "anonymbox.com",
76
+ "luxusmail.org",
77
+ "eyepaste.com",
78
+ "instant-email.org",
79
+ "easytrashmail.com",
80
+ "dockstones.com",
81
+ }
82
+
83
+ DISPOSABLE_PATTERNS = (
84
+ # Only keep the numeric timestamp pattern - remove the + pattern which flags legitimate aliases
85
+ re.compile(r"^[a-zA-Z0-9]+\.[0-9]{10}@"),
86
+ )
87
+
88
+ RBL_SERVICES = (
89
+ "multi.surbl.org",
90
+ "zen.spamhaus.org",
91
+ "b.barracudacentral.org",
92
+ "bl.spamcop.net",
93
+ "dnsbl.sorbs.net",
94
+ "dnsbl.httpbl.org",
95
+ )
96
+
97
+ BLOCKED_MX_RECORDS = {
98
+ "mx2.den.yt",
99
+ }
100
+
101
+ EMAIL_RE = re.compile(r"^[^@\s]{1,64}@[^@\s]{1,253}$")
102
+ CACHE_TTL_SECONDS = 60 * 60 * 24
103
+
104
+
105
+ @dataclass(frozen=True)
106
+ class EmailCheckResult:
107
+ email: str
108
+ is_disposable: bool
109
+ reason: str
110
+
111
+
112
+ def _normalize_email(email: str) -> Tuple[Optional[str], Optional[str], Optional[str]]:
113
+ normalized = email.strip().lower()
114
+ if not normalized or len(normalized) > 254 or not EMAIL_RE.match(normalized):
115
+ return None, None, "Invalid email format"
116
+
117
+ local_part, domain = normalized.rsplit("@", 1)
118
+ if not local_part or not domain or ".." in domain:
119
+ return None, None, "Invalid email format"
120
+
121
+ try:
122
+ ascii_domain = domain.encode("idna").decode("ascii")
123
+ except UnicodeError:
124
+ return None, None, "Invalid email domain"
125
+
126
+ return f"{local_part}@{ascii_domain}", ascii_domain, None
127
+
128
+
129
+ def is_valid_domain(domain: str) -> Tuple[bool, str]:
130
+ try:
131
+ mx_records = dns.resolver.resolve(domain, "MX")
132
+ except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
133
+ return False, "Invalid domain or no MX records"
134
+ except Exception:
135
+ logger.exception("Error checking domain MX records")
136
+ return False, "Error checking domain MX records"
137
+
138
+ for mx in mx_records:
139
+ mx_host = str(mx.exchange).rstrip(".").lower()
140
+ if mx_host in BLOCKED_MX_RECORDS:
141
+ return False, f"Blocked MX record: {mx_host}"
142
+
143
+ return True, "Valid MX record"
144
+
145
+
146
+ def check_rbl_ip(ip: str, rbl: str) -> bool:
147
+ """Check if IP address is listed in RBL service."""
148
+ try:
149
+ # Reverse IP octets for RBL query format
150
+ reversed_ip = ".".join(reversed(ip.split(".")))
151
+ query = f"{reversed_ip}.{rbl}"
152
+ dns.resolver.resolve(query, "A")
153
+ return True
154
+ except dns.resolver.NXDOMAIN:
155
+ return False
156
+ except Exception:
157
+ logger.exception("Error checking RBL for IP %s", ip)
158
+ return False
159
+
160
+
161
+ def check_rbl(domain: str) -> Tuple[bool, str]:
162
+ """Check if domain's MX IPs are listed in any RBL service."""
163
+ try:
164
+ # Get MX records first
165
+ mx_records = dns.resolver.resolve(domain, "MX")
166
+ except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers):
167
+ return False, "Cannot resolve MX records for RBL check"
168
+ except Exception:
169
+ logger.exception("Error resolving MX records for RBL check")
170
+ return False, "Error resolving MX records for RBL check"
171
+
172
+ # Check each MX record's IP against RBLs
173
+ for mx in mx_records:
174
+ mx_host = str(mx.exchange).rstrip(".").lower()
175
+
176
+ try:
177
+ # Resolve MX hostname to IP
178
+ a_records = dns.resolver.resolve(mx_host, "A")
179
+ for a_record in a_records:
180
+ ip = str(a_record)
181
+
182
+ # Check this IP against all RBL services
183
+ for rbl in RBL_SERVICES:
184
+ if check_rbl_ip(ip, rbl):
185
+ return True, f"MX IP {ip} listed in {rbl}"
186
+
187
+ except Exception:
188
+ logger.exception("Error resolving MX hostname %s to IP", mx_host)
189
+ continue
190
+
191
+ return False, "Not listed in any RBL"
192
+
193
+
194
+ def cache_result(domain: str, is_disposable: bool, reason: str) -> None:
195
+ try:
196
+ cache.set(
197
+ domain,
198
+ {"is_disposable": is_disposable, "reason": reason},
199
+ expire=CACHE_TTL_SECONDS,
200
+ )
201
+ except Exception:
202
+ logger.exception("Error caching domain result")
203
+
204
+
205
+ def is_disposable_email(email: str) -> Tuple[bool, str]:
206
+ result = check_email(email)
207
+ return result.is_disposable, result.reason
208
+
209
+
210
+ def check_email(email: str) -> EmailCheckResult:
211
+ normalized_email, domain, validation_error = _normalize_email(email)
212
+ if validation_error or not normalized_email or not domain:
213
+ return EmailCheckResult(
214
+ email=email,
215
+ is_disposable=True,
216
+ reason=validation_error or "Invalid email format",
217
+ )
218
+
219
+ # Check known trusted providers first - skip all other checks
220
+ if domain in KNOWN_NOT_DISPOSABLE_DOMAINS:
221
+ return EmailCheckResult(
222
+ email=normalized_email,
223
+ is_disposable=False,
224
+ reason="Known trusted provider",
225
+ )
226
+
227
+ # Check cache after trusted provider check
228
+ cached_result = cast(Any, cache.get(domain))
229
+ if cached_result:
230
+ return EmailCheckResult(
231
+ email=normalized_email,
232
+ is_disposable=bool(cached_result["is_disposable"]),
233
+ reason=str(cached_result["reason"]),
234
+ )
235
+
236
+ # Check if domain has valid MX records and is not using blocked MX hosts
237
+ is_valid, mx_reason = is_valid_domain(domain)
238
+ if not is_valid:
239
+ # Only cache permanent failures, not transient DNS errors
240
+ if not mx_reason.startswith("Error"):
241
+ cache_result(domain, True, mx_reason)
242
+ return EmailCheckResult(normalized_email, True, mx_reason)
243
+
244
+ # Check against known disposable domains list
245
+ if domain in DISPOSABLE_DOMAINS:
246
+ reason = "Known disposable domain"
247
+ cache_result(domain, True, reason)
248
+ return EmailCheckResult(normalized_email, True, reason)
249
+
250
+ # Check against disposable patterns
251
+ for pattern in DISPOSABLE_PATTERNS:
252
+ if pattern.match(normalized_email):
253
+ reason = "Matched disposable pattern"
254
+ cache_result(domain, True, reason)
255
+ return EmailCheckResult(normalized_email, True, reason)
256
+
257
+ # Check against RBLs (now properly checks MX IPs)
258
+ is_listed, rbl_reason = check_rbl(domain)
259
+ if is_listed:
260
+ cache_result(domain, True, rbl_reason)
261
+ return EmailCheckResult(normalized_email, True, rbl_reason)
262
+
263
+ # Domain appears legitimate - cache this result too
264
+ reason = "Not disposable"
265
+ cache_result(domain, False, reason)
266
+ return EmailCheckResult(normalized_email, False, reason)
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: temp-email-filter
3
+ Version: 0.1.1
4
+ Summary: Detect disposable email addresses with domain, MX, pattern, and RBL checks.
5
+ Author: Temp Email Filter contributors
6
+ License-Expression: MIT
7
+ Keywords: email,disposable-email,validation,dns,temporary-email
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Communications :: Email
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: diskcache>=5.6
20
+ Requires-Dist: dnspython>=2.6
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=8.0; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # Temp Email Filter
26
+
27
+ Temp Email Filter is a Python package for detecting disposable email addresses. It performs comprehensive checks including known disposable domains, trusted provider validation, email patterns, MX record validation, blocked MX hosts, and DNS-based RBL (Real-time Blackhole List) services.
28
+
29
+ ## Features
30
+
31
+ - **Trusted provider check**: Immediately validates emails from known legitimate providers (Gmail, Yahoo, etc.)
32
+ - **Disposable domain detection**: Checks against known temporary email services
33
+ - **Pattern analysis**: Identifies suspicious email patterns (excludes legitimate + aliases)
34
+ - **MX record validation**: Verifies domain has valid mail servers
35
+ - **RBL checking**: Queries reputation databases using proper IP-based lookups
36
+ - **Intelligent caching**: Caches both positive and negative results for performance
37
+ - **DNS timeouts**: Prevents hanging on slow DNS queries
38
+ - **Input validation**: Normalizes and validates email format before processing
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ pip install temp-email-filter
44
+ ```
45
+
46
+ For local development from a clone:
47
+
48
+ ```bash
49
+ git clone <your-repo-url>
50
+ cd temp_email_filter
51
+ python -m venv .venv
52
+ source .venv/bin/activate
53
+ pip install -e ".[dev]"
54
+ ```
55
+
56
+ ## Basic Usage
57
+
58
+ ```python
59
+ from temp_email_filter import check_email
60
+
61
+ result = check_email("test@gmail.com")
62
+ print(f"Email: {result.email}")
63
+ print(f"Is disposable: {result.is_disposable}")
64
+ print(f"Reason: {result.reason}")
65
+
66
+ if result.is_disposable:
67
+ print("This email should be rejected")
68
+ else:
69
+ print("This email is acceptable")
70
+ ```
71
+
72
+ If you only need the original tuple style:
73
+
74
+ ```python
75
+ from temp_email_filter import is_disposable_email
76
+
77
+ is_disposable, reason = is_disposable_email("test@gmail.com")
78
+ ```
79
+
80
+ ## Django Example
81
+
82
+ ```python
83
+ from django.core.exceptions import ValidationError
84
+ from temp_email_filter import check_email
85
+
86
+
87
+ def validate_non_disposable_email(value):
88
+ result = check_email(value)
89
+ if result.is_disposable:
90
+ raise ValidationError(result.reason)
91
+ ```
92
+
93
+ Use the validator in a model or serializer field.
94
+
95
+ ## FastAPI Example
96
+
97
+ This package does not start a FastAPI server. Add it to your own FastAPI app:
98
+
99
+ ```python
100
+ from fastapi import FastAPI, HTTPException
101
+ from temp_email_filter import check_email
102
+
103
+ app = FastAPI()
104
+
105
+
106
+ @app.get("/email-checker")
107
+ def email_checker(email: str):
108
+ result = check_email(email)
109
+ if result.reason.startswith("Invalid email"):
110
+ raise HTTPException(status_code=400, detail=result.reason)
111
+
112
+ return {
113
+ "email": result.email,
114
+ "is_disposable": result.is_disposable,
115
+ "reason": result.reason,
116
+ }
117
+ ```
118
+
119
+ ## Running As A Service
120
+
121
+ This project is package-only. To run it as a service, install it inside your own Django, FastAPI, Flask, worker, or microservice project and call `check_email()` from your route, form validation, serializer, or background job.
122
+
123
+ ## Configuration
124
+
125
+ The cache directory defaults to `.email_cache`. Override it with:
126
+
127
+ ```bash
128
+ export TEMP_EMAIL_FILTER_CACHE_DIR=/tmp/temp-email-filter-cache
129
+ ```
130
+
131
+ ## Recent Improvements (v0.1.1)
132
+
133
+ - **Fixed RBL checking**: Now properly queries MX record IPs instead of domains
134
+ - **Improved pattern matching**: Removed false positive for legitimate + aliases (john+work@gmail.com)
135
+ - **Enhanced caching**: Now caches both disposable and legitimate results
136
+ - **Better logic flow**: Trusted providers checked first, bypassing expensive DNS operations
137
+ - **DNS timeouts**: Added configurable timeouts to prevent hanging queries
138
+ - **Transient error handling**: Avoids caching temporary DNS failures
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ pip install -e ".[dev]"
144
+ pytest
145
+ ```
146
+
147
+ ## Publishing
148
+
149
+ Maintainer release steps are documented in [`docs/PYPI.md`](docs/PYPI.md).
150
+
151
+ ## License
152
+
153
+ MIT
@@ -0,0 +1,15 @@
1
+ CONTRIBUTING.md
2
+ LICENSE
3
+ MANIFEST.in
4
+ README.md
5
+ SECURITY.md
6
+ pyproject.toml
7
+ docs/PYPI.md
8
+ src/email_span_filter/py.typed
9
+ src/temp_email_filter/__init__.py
10
+ src/temp_email_filter/checker.py
11
+ src/temp_email_filter.egg-info/PKG-INFO
12
+ src/temp_email_filter.egg-info/SOURCES.txt
13
+ src/temp_email_filter.egg-info/dependency_links.txt
14
+ src/temp_email_filter.egg-info/requires.txt
15
+ src/temp_email_filter.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ diskcache>=5.6
2
+ dnspython>=2.6
3
+
4
+ [dev]
5
+ pytest>=8.0
@@ -0,0 +1,3 @@
1
+ email_spam_filter
2
+ email_span_filter
3
+ temp_email_filter