Integrate Email Insights into Your Python Application: A Practical Guide
This guide shows how to integrate Email Insights into a Python application using the official SDK. You will learn how to run single validations, launch batch jobs with polling, handle errors safely, and ship production-ready code with logging and secure configuration.
If you are new to the product, start with the overview at /products/email-insights and the bulk features at /email-insights/bulk-email-validation. For security and compliance details, see /security and /legal/gdpr-notice.
Requirements
- Python 3.8 or later
- An API key from your account settings
Install the SDK:
pip install opportify-sdk
Quick start: single email analysis
import os
from opportify_sdk import EmailInsights
api_key = os.environ["OPPORTIFY_API_KEY"] # Load securely per your policy
client = EmailInsights(api_key).set_version("v1")
params = {
"email": "user@example.com",
"enableAutoCorrection": True,
"enableAi": True
}
try:
result = client.analyze(params)
print({
"isDeliverable": result.get("isDeliverable"), # "yes" | "no" | "unknown"
"isCatchAll": result.get("isCatchAll"),
"isMailboxFull": result.get("isMailboxFull"),
"isReachable": result.get("isReachable"),
"normalizedScore": result.get("normalizedScore"),
"riskLevel": result.get("riskLevel") or (result.get("riskReport", {}) or {}).get("level"),
"provider": result.get("provider"),
"domain": result.get("domain")
})
except Exception as e:
raise SystemExit(f"Email analysis failed: {e}")
When to enable AI and autocorrection
enableAutoCorrection=Truehelps correct common user typos in form inputs.enableAi=Trueadds domain enrichment and risk signals that improve decisions for gating, segmentation, and fraud protection.
Batch analysis with safe polling
Use batch jobs for large lists. A job returns a job_id that you can poll until results are ready.
import os, time
from typing import Dict, Any
from opportify_sdk import EmailInsights
api_key = os.environ["OPPORTIFY_API_KEY"]
client = EmailInsights(api_key).set_version("v1").set_debug_mode(False)
batch_params = {
"emails": [
"user1@company.com",
"user2@domain.org",
"test@example.com"
],
"enableAutoCorrection": True,
"enableAi": True
}
def wait_for_completion(c: EmailInsights, job_id: str, max_wait_sec: int = 600) -> Dict[str, Any]:
start = time.time()
delay = 2.0
while True:
status = c.get_batch_status(job_id)
state = status.get("status")
progress = status.get("progress", 0)
print(f"State={state} Progress={progress}%")
if state == "COMPLETED":
return status
if state == "ERROR":
desc = status.get("status_description", "unknown error")
raise RuntimeError(f"Batch job failed: {desc}")
if time.time() - start > max_wait_sec:
raise TimeoutError("Batch job did not complete within the timeout period")
time.sleep(min(delay, 15.0) * 1.2) # mild backoff
delay = min(delay * 1.6, 30.0)
try:
resp = client.batch_analyze(batch_params)
job_id = resp["job_id"]
final = wait_for_completion(client, job_id)
downloads = final.get("download_urls", {})
print("CSV:", downloads.get("csv"))
print("JSON:", downloads.get("json"))
except Exception as e:
raise SystemExit(f"Batch analysis failed: {e}")
Practical tips for batches
- Split uploads by dedup risk to keep jobs small and predictable.
- Persist
job_idin your datastore so you can reconcile results even if a worker restarts. - If you run multiple concurrent batches, throttle polling to avoid unnecessary API calls.
Interpreting results and taking action
Deliverability is tri-state
isDeliverable returns a string: yes, no, or unknown. Combine it with isMailboxFull, isCatchAll, and isReachable to decide your next action.
Risk model
Email Insights provides a normalizedScore on a 200 to 1000 scale along with a static risk bucket:
| Risk Level | Score Range | Guidance |
|---|---|---|
| Lowest | ≤ 300 | Safe to include in primary sends |
| Low | 300-400 | Generally safe; monitor |
| Medium | 400-600 | Consider revalidation or secondary flows |
| High | 600-800 | Exclude from high value campaigns; verify |
| Highest | > 800 | Exclude; investigate fraud or traps |
Decision helper (tri-state aware)
def classify_contact(result: dict) -> str:
"""
Return a routing label: accept | revalidate | review | reject.
Uses tri-state isDeliverable: yes | no | unknown.
"""
deliverable = (result.get("isDeliverable") or "").lower()
risk_level = (result.get("riskLevel")
or (result.get("riskReport", {}) or {}).get("level")
or "").lower()
is_catch_all = bool(result.get("isCatchAll"))
is_mailbox_full = bool(result.get("isMailboxFull"))
is_reachable = bool(result.get("isReachable"))
signals = result.get("signals", {}) or {}
is_disposable = bool(signals.get("isDisposableDomain", False))
if is_disposable or risk_level == "highest":
return "reject"
if deliverable == "no":
return "revalidate" if is_mailbox_full else "reject"
if deliverable == "unknown":
if risk_level == "high":
return "review"
if is_catch_all or not is_reachable:
return "revalidate"
return "review"
# deliverable == "yes"
if risk_level == "high":
return "review"
if risk_level == "medium" or is_catch_all:
return "revalidate"
return "accept"
Production hardening
Configuration and secrets
- Store your
OPPORTIFY_API_KEYsecurely using your preferred method for secret management. How you load or access it in your application environment is up to you. - Never hard-code the key or commit it to version control.
- Avoid printing or logging the full key or payloads that include personal data.
Access control Email Insights supports API Key Access Control Lists (ACLs) that allow you to restrict usage by IP address or range. Configure these in your account settings to ensure only approved systems can access the API.
Timeouts and retries
- Wrap calls with retry logic at the workflow level for transient network failures.
- Use exponential backoff and keep your side effects idempotent.
Observability
- Enable
.set_debug_mode(True)in non-production or when troubleshooting. - Log
job_id,status, and summary counts only. Avoid storing raw personal data unless necessary.
Data protection
- Treat email addresses as personal data. Follow privacy guidance at /legal/gdpr-notice and security practices at /security.
- Minimize retention. Store only what you need for decisions.
Local testing checklist
- Use synthetic emails that cover edge cases: disposable domains, catch-all domains, new corporate domains, common typos.
- Verify routing logic for each
riskLeveland eachisDeliverablevalue. - Confirm graceful handling of invalid keys, timeouts, and retries.
Where to go next
- Product overview: /products/email-insights
- Bulk validations: /email-insights/bulk-email-validation
- Security and compliance: /security and /legal/gdpr-notice
- Integrations: /integrations/zapier, /integrations/hubspot, /integrations/crisp
Reference: SDK entry points used
EmailInsights(api_key).set_version("v1").analyze(params)EmailInsights(...).batch_analyze(batch_params)EmailInsights(...).get_batch_status(job_id).set_debug_mode(True)for verbose troubleshooting