Python SDK for the EmailKind API — classify emails by provider and type
Project description
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
Project details
Release history Release notifications | RSS feed
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 emailkind-0.2.0.tar.gz.
File metadata
- Download URL: emailkind-0.2.0.tar.gz
- Upload date:
- Size: 14.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
495556e94bade87976a8bf6b52fe6f5f98dbdae90e209e0e29aa3656cd46243f
|
|
| MD5 |
ab14600ba6c7adc41d994eed27d86df9
|
|
| BLAKE2b-256 |
b9bb7dad8d6775d8c3b24a9ded68edfccd341bd40d546b1d1a7f1b77aaa5745c
|
File details
Details for the file emailkind-0.2.0-py3-none-any.whl.
File metadata
- Download URL: emailkind-0.2.0-py3-none-any.whl
- Upload date:
- Size: 11.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49878df536a1e3441d0bbcdd7d58e81bf00bbff6746f96b5ef276f5cbfd6809e
|
|
| MD5 |
e58c9d7943f96f2332dd501f3e5d1c33
|
|
| BLAKE2b-256 |
84f38cab37104545ad74ba7888775429aff338bf1cc1a8469dff121d5adadc34
|