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.
- temp_email_filter-0.1.1/CONTRIBUTING.md +20 -0
- temp_email_filter-0.1.1/LICENSE +21 -0
- temp_email_filter-0.1.1/MANIFEST.in +21 -0
- temp_email_filter-0.1.1/PKG-INFO +153 -0
- temp_email_filter-0.1.1/README.md +129 -0
- temp_email_filter-0.1.1/SECURITY.md +12 -0
- temp_email_filter-0.1.1/docs/PYPI.md +94 -0
- temp_email_filter-0.1.1/pyproject.toml +44 -0
- temp_email_filter-0.1.1/setup.cfg +4 -0
- temp_email_filter-0.1.1/src/email_span_filter/py.typed +1 -0
- temp_email_filter-0.1.1/src/temp_email_filter/__init__.py +3 -0
- temp_email_filter-0.1.1/src/temp_email_filter/checker.py +266 -0
- temp_email_filter-0.1.1/src/temp_email_filter.egg-info/PKG-INFO +153 -0
- temp_email_filter-0.1.1/src/temp_email_filter.egg-info/SOURCES.txt +15 -0
- temp_email_filter-0.1.1/src/temp_email_filter.egg-info/dependency_links.txt +1 -0
- temp_email_filter-0.1.1/src/temp_email_filter.egg-info/requires.txt +5 -0
- temp_email_filter-0.1.1/src/temp_email_filter.egg-info/top_level.txt +3 -0
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -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 @@
|
|
|
1
|
+
|