proofwire
Email, phone and IP validation for Python.
pip install proofwire
from proofwire import Proofwire
proofwire = Proofwire() # reads PROOFWIRE_API_KEY
result = proofwire.email("someone@example.com")
action = result.match(
valid=lambda r: "send",
invalid=lambda r: "drop",
unknown=lambda r: "ask them to confirm the address",
)
No dependencies. It gets installed into other people's services, and a validation client that drags in a transitive tree is one that eventually breaks a build for reasons unrelated to validation.
The one design decision worth knowing
There is no result.valid.
That attribute would be the most convenient thing this package could offer and
the most damaging, because if result.valid: files every inconclusive answer
under "not valid" — and inconclusive is the case the product exists to
surface. A large minority of business mail servers accept every address you ask
about, so nothing observable distinguishes a real mailbox from a fictional one.
Most validators resolve that into "valid" and invoice you. You find out when it
bounces.
So verdicts have three states, and match takes all three as required
keyword-only arguments. Leave one out and it fails at the call site:
TypeError: match() missing 1 required keyword-only argument: 'unknown'
Python cannot check exhaustiveness the way a compiler can. This is the closest thing that fails early and loudly rather than three weeks later in a bounce report.
Inconclusive verdicts are never billed. You are not paying for the honesty.
What a result carries
result = proofwire.email("j.smith@thecompany.com")
result.verdict # 'unknown'
result.confidence # 0.52
result.attributes["catchAll"] # True
result.billing.credits_charged # 0
result.billing.reason # why it cost what it did, in plain language
result.is_inconclusive # True
print(result.explain())
UNKNOWN - j.smith@thecompany.com
confidence 52%, risk 10/100, 0 credits (inconclusive verdicts are not billed)
[syntax] Address is syntactically well formed. (+0.80)
[mx] 1 MX record published. (+1.90)
[smtp] Control probe: the server also accepted an address that cannot exist,
so its acceptance carries no information. (-1.40)
explain() is written to be pasted into a log line or a reply to a customer
asking why you would not send to them.
The control probe is the part worth noticing. Before trusting an acceptance, the server is asked about an address that cannot exist. If that is accepted too, the acceptance of the real one means nothing, and the verdict says so.
Retries and double charges
Every call carries an idempotency key, generated for you. A retry after a timeout replays the original response instead of spending again, so the client retries on 5xx and 429 by default without risking a double charge. A 429 is respected by the header it came with, not by a guess.
Pass your own key when a retry has to survive a process restart:
proofwire.email(address, idempotency_key=f"signup:{user_id}")
Errors
Separated by what you should do about them, because a malformed key is a deploy problem, an empty balance is a billing problem, and a 502 is a wait-and-retry problem.
AuthenticationError |
Key missing, malformed or revoked |
InsufficientCreditsError |
Out of credits, or past your spend cap |
InvalidRequestError |
The value or the request is wrong |
RateLimitError |
Carries retry_after_seconds |
ServiceError |
Our side; already retried |
ConnectionError |
Never reached us |
An inconclusive verdict is not among them. It is a successful response.
Configuration
Proofwire(
api_key="pk_live_...", # or PROOFWIRE_API_KEY
timeout=15.0,
max_retries=2,
)
A pk_test_ key answers from fixed sandbox fixtures and is never billed, which
is what makes it usable in a test suite. proofwire.is_test_mode tells you
which kind you have.
Links
- Documentation
- Published accuracy benchmark, with the dataset downloadable so you can rerun it
- MCP server, if you are wiring this into an agent
- Source and issues
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file proofwire-0.1.0.tar.gz.
File metadata
- Download URL: proofwire-0.1.0.tar.gz
- Upload date:
- Size: 9.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
157a8af11cd120c7731bba23ac85edbdfdf6c8014884c7bd46dcd9fa67d1fca8
|
|
| MD5 |
ae17c9937f7c0ed373774189a2d22f51
|
|
| BLAKE2b-256 |
603aee7b4b00397e123b1d3f28166d5e2ade2752db36207e9deb8666a280e017
|
File details
Details for the file proofwire-0.1.0-py3-none-any.whl.
File metadata
- Download URL: proofwire-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b20f9108d9b315abb0da2cebe57d479b9ad495ff3b233e3dd150ce7bb8e8459f
|
|
| MD5 |
a1620e8e03cb63259ceb5a93e11e0a10
|
|
| BLAKE2b-256 |
f4559941daba2570cb82506777d528a4ce67ab40db49468b9728d86cb62f1ab5
|