Skip to main content

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

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

proofwire-0.1.0.tar.gz (9.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

proofwire-0.1.0-py3-none-any.whl (12.6 kB view details)

Uploaded Python 3

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

Hashes for proofwire-0.1.0.tar.gz
Algorithm Hash digest
SHA256 157a8af11cd120c7731bba23ac85edbdfdf6c8014884c7bd46dcd9fa67d1fca8
MD5 ae17c9937f7c0ed373774189a2d22f51
BLAKE2b-256 603aee7b4b00397e123b1d3f28166d5e2ade2752db36207e9deb8666a280e017

See more details on using hashes here.

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

Hashes for proofwire-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b20f9108d9b315abb0da2cebe57d479b9ad495ff3b233e3dd150ce7bb8e8459f
MD5 a1620e8e03cb63259ceb5a93e11e0a10
BLAKE2b-256 f4559941daba2570cb82506777d528a4ce67ab40db49468b9728d86cb62f1ab5

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page