emailkind
Python SDK to classify email addresses by provider (Gmail vs Google Workspace, Outlook.com vs Microsoft 365), type (business / personal / disposable / education), and company — using only passive DNS analysis.
Unlike email verification APIs that ping mailboxes, EmailKind uses MX, SPF, and TLS certificate analysis to identify the exact provider and type behind any email address — without sending anything. 150+ providers detected, 57,000+ disposable domains tracked, company enrichment included.
Installation
pip install emailkind
Get an API key
Free tier with 100 calls/month, no credit card required.
https://emailkind.com/register
Quick start
from emailkind import EmailKind
client = EmailKind("sk_live_xxx")
result = client.classify(email="ceo@stripe.com")
print(result.provider.name) # "Google Workspace"
print(result.provider.type) # "business"
print(result.classification.is_business) # True
print(result.confidence) # 0.98
What you get back
Every classify call returns a rich, structured result:
result = client.classify(email="ceo@stripe.com", enrich=True)
# Provider detection — not just "Google", but Google Workspace vs Gmail
result.provider.id # "google_workspace"
result.provider.name # "Google Workspace"
result.provider.type # "business"
# Classification flags
result.classification.is_business # True
result.classification.is_free # False
result.classification.is_disposable # False
result.classification.is_education # False
# Company enrichment (when enrich=True)
result.company.name # "Stripe, Inc."
result.company.source # "ssl"
# Confidence score
result.confidence # 0.98
# Raw MX records
result.mx # ["aspmx.l.google.com", ...]
Recipes
Block disposable email signups in FastAPI
from fastapi import FastAPI, HTTPException
from emailkind import EmailKind
app = FastAPI()
ek = EmailKind() # reads EMAILKIND_API_KEY from env
@app.post("/signup")
def signup(email: str):
result = ek.classify(email=email)
if result.classification.is_disposable:
raise HTTPException(400, "Disposable emails are not allowed")
# proceed with registration...
Qualify B2B leads in your signup webhook
from emailkind import EmailKind
client = EmailKind("sk_live_xxx")
def handle_signup(email: str):
result = client.classify(email=email, enrich=True)
if result.classification.is_business:
create_lead(
email=email,
company=result.company.name if result.company else None,
provider=result.provider.name,
confidence=result.confidence,
)
notify_sales(email)
else:
start_self_serve_onboarding(email)
Detect Google Workspace vs Gmail accounts
result = client.classify(email="user@company.com")
# Same MX records, but EmailKind distinguishes them
if result.provider.id == "google_workspace":
print("Paid Google Workspace — likely a real business")
elif result.provider.id == "gmail":
print("Free Gmail account")
Enrich CRM leads with company names
import csv
from emailkind import EmailKind
client = EmailKind("sk_live_xxx")
with open("leads.csv") as f:
for row in csv.DictReader(f):
result = client.classify(email=row["email"], enrich=True)
if result.company:
print(f"{row['email']} -> {result.company.name}")
Configuration
from emailkind import EmailKind
# From argument
client = EmailKind("sk_live_xxx")
# From environment variable
# export EMAILKIND_API_KEY=sk_live_xxx
client = EmailKind()
# Custom options
client = EmailKind(
api_key="sk_live_xxx",
base_url="https://custom.example.com", # self-hosted or proxy
timeout=10, # seconds (default: 30)
)
Classify
# By email
result = client.classify(email="user@gmail.com")
# By domain
result = client.classify(domain="stripe.com")
# With company enrichment
result = client.classify(email="ceo@stripe.com", enrich=True)
print(result.company.name) # "Stripe, Inc."
Batch classification
Classify up to 100 emails or domains in a single request:
batch = client.classify_batch(
emails=["ceo@stripe.com", "user@gmail.com"],
domains=["notion.so"],
enrich=True,
)
for item in batch.results:
print(f"{item.input} -> {item.provider.name} ({item.provider.type})")
# ceo@stripe.com -> Google Workspace (business)
# user@gmail.com -> Gmail (personal)
# notion.so -> Cloudflare (business)
Custom rules
Override classifications for specific domains or MX patterns (paid plans):
# List rules
rules = client.list_rules()
# Create a rule
rule = client.create_rule(
match_type="domain",
match_value="internal.company.com",
provider_name="Internal Mail",
provider_type="business",
)
# Delete a rule
client.delete_rule(rule.id)
Bulk processing
Upload a CSV for async classification of large datasets (paid plans):
import time
from emailkind import EmailKind
client = EmailKind("sk_live_xxx")
# Upload
job = client.bulk_upload("emails.csv", enrich=True)
print(f"Job {job.id} started")
# Poll until completion
while True:
status = client.bulk_status(job.id)
print(f"Progress: {status.processed}/{status.total} ({status.status})")
if status.status in ("completed", "failed"):
break
time.sleep(5)
# Download results
if status.status == "completed":
csv_bytes = client.bulk_results(job.id)
with open("results.csv", "wb") as f:
f.write(csv_bytes)
print("Results saved to results.csv")
Error handling
All API errors raise typed exceptions with structured context:
from emailkind import (
EmailKindError,
AuthenticationError,
RateLimitError,
ValidationError,
ForbiddenError,
NotFoundError,
)
try:
result = client.classify(email="user@example.com")
except AuthenticationError:
# Invalid or missing API key (401)
pass
except RateLimitError as e:
# Too many requests (429)
print(f"Retry after {e.retry_after}s")
except ValidationError:
# Bad request parameters (400)
pass
except ForbiddenError:
# Plan doesn't support this feature (403)
pass
except NotFoundError:
# Resource not found (404)
pass
except EmailKindError as e:
# Catch-all for any API error
print(e.status_code, e.code, e.message, e.request_id)
Every exception exposes:
| Attribute | Type | Description |
|---|---|---|
message |
str |
Human-readable error description |
code |
str |
Machine-readable error code |
status_code |
int |
HTTP status code |
request_id |
str |
Unique ID for support |
retry_after |
int |
Seconds to wait (only on RateLimitError) |
Retry with backoff
import time
from emailkind import EmailKind, RateLimitError
client = EmailKind("sk_live_xxx")
def classify_with_retry(email, max_retries=3):
for attempt in range(max_retries):
try:
return client.classify(email=email)
except RateLimitError as e:
if attempt == max_retries - 1:
raise
time.sleep(e.retry_after or (2 ** attempt))
Use with AI agents (MCP)
EmailKind exposes a native Model Context Protocol server for Claude Desktop, Cursor, and Windsurf. AI agents can classify emails, detect providers, and enrich company data without writing code.
{
"mcpServers": {
"emailkind": {
"url": "https://emailkind.com/v1/mcp",
"headers": { "Authorization": "Bearer sk_live_xxx" }
}
}
}
See MCP documentation for setup instructions.
Sandbox mode
Use test keys to develop without affecting your quota:
client = EmailKind("sk_test_xxx")
result = client.classify(email="user@gmail.com")
# Works identically, but usage is not tracked
Performance
| Metric | Value |
|---|---|
| Response time | < 50ms (p99) |
| Uptime | 99.9% (last 90 days) |
| Providers detected | 150+ |
| Disposable domains | 57,000+ (updated daily) |
| Infrastructure | EU (Germany) |
Rate limits depend on your plan. The SDK raises RateLimitError with a retry_after value when exceeded.
Roadmap
-
AsyncEmailKindclient for asyncio / FastAPI workloads - Built-in retry with configurable backoff
- Webhook signature verification helper
FAQ
What's the difference between EmailKind and email verification? Email verification pings mailboxes to check if an address exists. EmailKind uses passive DNS analysis to tell you what kind of email it is — provider, type, company — without sending anything. Use them together or separately. Learn more
Does EmailKind work with disposable email lists?
Yes. EmailKind tracks 57,000+ disposable domains, updated daily. The is_disposable flag covers all major throwaway services. You can also add your own domains via custom rules.
Can I self-host EmailKind? Not currently. EmailKind is a hosted API. For enterprise deployments with specific requirements, contact us.
Other SDKs
| Language | Package |
|---|---|
| Node.js | emailkind |
| Go | emailkind-go |
| REST | API docs |
Contributing
Bug reports and pull requests are welcome on GitHub Issues.
For bug fixes, feel free to open a PR directly. For new features, please open an issue first to discuss the approach.
License
Metadata
Release files for emailkind 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| emailkind-0.2.0.tar.gz | 14.3 kB | Without provenance | Details for emailkind-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| emailkind-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for emailkind-0.2.0-py3-none-any.whl |
Total release size: 25.9 kB
Release files / emailkind-0.2.0.tar.gz
| Download URL | emailkind-0.2.0.tar.gz |
|---|---|
| Size | 14.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
495556e94bade87976a8bf6b52fe6f5f98dbdae90e209e0e29aa3656cd46243f
|
|
BLAKE2b-256 checksum How to use checksums |
b9bb7dad8d6775d8c3b24a9ded68edfccd341bd40d546b1d1a7f1b77aaa5745c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / emailkind-0.2.0-py3-none-any.whl
| Download URL | emailkind-0.2.0-py3-none-any.whl |
|---|---|
| Size | 11.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
49878df536a1e3441d0bbcdd7d58e81bf00bbff6746f96b5ef276f5cbfd6809e
|
|
BLAKE2b-256 checksum How to use checksums |
84f38cab37104545ad74ba7888775429aff338bf1cc1a8469dff121d5adadc34
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Security for emailkind 0.2.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.