Skip to main content

📧 emailtoolkit

PyPI Version Python Versions CI Status License Typing: PEP 561

RFC‑aware email parsing, normalization, extraction, and DNS health checks with a clean, phonenumbers‑style API.


✨ Design goals

  • Simple API Be as easy as phonenumbers. Import module‑level functions for quick tasks, or instantiate EmailTools for tuned, high‑performance use.

  • Practical validation Separate syntax validation (via email_validator) from deliverability checks. Enforce your own DNS policy (require MX, or allow A/AAAA fallback).

  • Provider‑aware identity Correctly determine that test.user@gmail.com and testuser+sales@googlemail.com are the same identity using canonicalization rules.

  • Operations‑ready Native env, .env, and config.json support; PII‑safe logging; TTL‑cached DNS; robust CLI.


⭐ Features

  • Automatic Cloudflare Decoding: Transparently finds and decodes Cloudflare-protected email addresses from HTML.
  • Robust Extraction: Discovers emails from free text, mailto: links, and other common formats.
  • Canonical Identity: Intelligently compares emails, understanding that test.user@gmail.com is the same as testuser+sales@googlemail.com.
  • DNS Health Checks: Validates domain deliverability by checking for MX and A/AAAA records.
  • Disposable Domain Filtering: Flags or blocks emails from known disposable providers.
  • Configurable: Fine-tune behavior with environment variables, .env files, or a config.json.

🚀 Installation

pip install emailtoolkit
# extras for DNS and .env support
pip install "emailtoolkit[dns,dotenv]"

🧪 Quick start

import emailtoolkit as et

# Validate
et.is_valid("Test.User+sales@Gmail.com")  # True

# Canonical form (provider‑specific rules)
et.canonical("t.e.s.t+sales@googlemail.com")  # "test@gmail.com"

# Compare by canonical identity
et.compare("t.e.s.t+sales@googlemail.com", "test@gmail.com")  # True

# Extract from free text (returns Email objects)
found = et.extract("Contact a@example.com, A@EXAMPLE.com, and junk@@bad.")
print([e.normalized for e in found])  # ["a@example.com", "A@example.com"]

🛠️ Command‑line interface (CLI)

# Canonical form
emailtoolkit canonical "t.e.s.t+bar@googlemail.com"
# → test@gmail.com

# Domain DNS health (JSON)
emailtoolkit domain example.com
# {
#   "domain": "example.com",
#   "ascii_domain": "example.com",
#   "mx_hosts": [],
#   "a_hosts": ["93.184.216.34"],
#   "has_mx": false,
#   "has_a": true,
#   "disposable": false
# }

# Extract from stdin
echo "Contact me at a@example.com" | emailtoolkit extract

⚙️ Configuration

Load precedence:

  1. Environment variables (e.g., EMAILTK_LOG_LEVEL)
  2. .env in the working directory (requires dotenv extra)
  3. config.json (when passed to CLI --config or build_tools("/path/to/config.json"))
  4. Internal defaults

Environment variables (full)

Variable Type Default Description
EMAILTK_LOG_LEVEL str INFO Logging level: DEBUG INFO WARNING ERROR
EMAILTK_REQUIRE_MX bool true If true, deliverability requires MX. If false, MX or A/AAAA is enough
EMAILTK_REQUIRE_DELIVERABILITY bool false If true, parse raises if deliverability fails
EMAILTK_ALLOW_SMTPUTF8 bool true Allow UTF‑8 local parts per RFC 6531
EMAILTK_DNS_TIMEOUT_SECONDS float 2.0 DNS timeout seconds
EMAILTK_DNS_TTL_SECONDS int 900 TTL for cached DNS answers
EMAILTK_USE_DNSPYTHON bool true Use dnspython when available
EMAILTK_EXTRACT_UNIQUE bool true Deduplicate by canonical form during extraction
EMAILTK_EXTRACT_MAX_RESULTS int or empty empty Hard cap on extractor results. Empty or 0 means no cap
EMAILTK_NORMALIZE_CASE bool true Lowercase domain on normalize
EMAILTK_GMAIL_CANON bool true Apply Gmail dot and plus canonicalization rules
EMAILTK_TREAT_DISPOSABLE_AS_INVALID bool false If true, disposable domains cause parse to raise
EMAILTK_BLOCK_PRIVATE_TLDS bool false Enforce known public suffixes if provided
EMAILTK_PUBLIC_SUFFIX_FILE path empty File with known public suffixes, one per line
EMAILTK_DISPOSABLE_SOURCE file://... or url://... or none none Source for disposable domains
EMAILTK_ENABLE_SMTP_PROBE bool false Reserved for optional SMTP probing module
EMAILTK_SMTP_PROBE_TIMEOUT float 3.0 Probe timeout
EMAILTK_SMTP_PROBE_CONCURRENCY int 5 Probe concurrency
EMAILTK_SMTP_PROBE_HELO str example.com HELO/EHLO identity
EMAILTK_PII_REDACT_LOGS bool true Mask emails in logs and exceptions
EMAILTK_PII_REDACT_STYLE mask or none mask Redaction style

See .env.example for a ready‑to‑copy template.


🧱 Disposable domain filtering

Create a text file and point to it:

# disposable.txt
# Lines beginning with # are comments
# Domains are matched case‑insensitively on ASCII form
mailinator.com
10minutemail.com
sharklasers.com

Enable via .env:

EMAILTK_DISPOSABLE_SOURCE=file://./disposable.txt

Optionally set:

EMAILTK_TREAT_DISPOSABLE_AS_INVALID=true

This will raise EmailParseException when parsing addresses on those domains.


🤖 Agents, MCP servers, and tool‑calling

from pydantic import BaseModel, Field
import emailtoolkit as et

class EmailInput(BaseModel):
    email: str = Field(..., description="Email address to parse")

class DomainInput(BaseModel):
    domain: str = Field(..., description="Domain to inspect")

def tool_parse(args: EmailInput):
    e = et.parse(args.email)
    return {
        "normalized": e.normalized,
        "canonical": e.canonical,
        "deliverable": e.deliverable_dns,
        "domain": e.domain_info.ascii_domain,
    }

def tool_domain(args: DomainInput):
    d = et.domain_health(args.domain)
    return {
        "domain": d.ascii_domain,
        "has_mx": d.has_mx,
        "has_a": d.has_a,
        "disposable": d.disposable,
    }

📚 API surface

import emailtoolkit as et
from emailtoolkit import EmailTools, Email, DomainInfo, EmailParseException

# module functions
et.parse(raw: str) -> Email
et.is_valid(raw: str) -> bool
et.normalize(raw: str) -> str
et.canonical(raw: str) -> str
et.extract(text: str) -> list[Email]
et.compare(a: str, b: str) -> bool
et.domain_health(domain: str) -> DomainInfo
et.build_tools(overrides_path: str | None = None) -> EmailTools

# dataclasses
Email(
  original, local, domain, ascii_email, normalized, canonical,
  domain_info: DomainInfo, valid_syntax: bool, deliverable_dns: bool, reason: str|None
)
DomainInfo(domain, ascii_domain, mx_hosts, a_hosts, has_mx, has_a, disposable)

🔒 Security & privacy

  • PII redaction in logs is on by default (EMAILTK_PII_REDACT_LOGS).
  • Avoid logging raw addresses in your application.
  • If SMTP probing is enabled in the future, keep it opt‑in, rate‑limited, and legally reviewed.

🧰 Development

pip install -e ".[dns,dotenv]" pytest ruff mypy
ruff check src
mypy src/emailtoolkit
pytest -q

🙏 Acknowledgments

Built on:

  • email_validator by Joshua Tauberer (Unlicense)
  • dnspython (ISC) [optional]
  • idna (BSD‑3‑Clause)

See THIRD_PARTY_NOTICES.md for license texts.


📦 License

MIT. See LICENSE. Third‑party licenses in THIRD_PARTY_NOTICES.md.


⭐ Support

If this toolkit helps you, star the repo and share it. Issues and PRs welcome.

Release files for emailtoolkit 0.1.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for emailtoolkit 0.1.6
File Size Uploaded
emailtoolkit-0.1.6.tar.gz 18.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for emailtoolkit 0.1.6
File Interpreter ABI Platform
emailtoolkit-0.1.6-py3-none-any.whl Python 3 none any Details

Total release size: 40.8 kB

Release files / emailtoolkit-0.1.6.tar.gz

Download URL emailtoolkit-0.1.6.tar.gz
Size 18.0 kB
Tags Source
SHA-256 checksum
How to use checksums
11bc86af96d332a65e5a6d576306207896273410602e77e60dd0a379b1f9caaf
BLAKE2b-256 checksum
How to use checksums
f17cc597ba1daea1c5d9e4cd4f13a9782421aaa9d7955415d7f8c17a38655491
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 6, 2026.

Transparency log

Release files / emailtoolkit-0.1.6-py3-none-any.whl

Download URL emailtoolkit-0.1.6-py3-none-any.whl
Size 22.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
20417f10a3f5440d049ad94dba894dee913c71afb2cc31d1f158cdc15f4a5d2c
BLAKE2b-256 checksum
How to use checksums
2c6f15d88a85d67f6ea21c55c7223e7f93cd1fca96a25272c5b830b07d1d12e5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.6 This release

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release 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