Integrate Email Insights into Your Python Application: A Practical Guide

5 min readOpportify Team

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=True helps correct common user typos in form inputs.
  • enableAi=True adds 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_id in 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_KEY securely 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 riskLevel and each isDeliverable value.
  • Confirm graceful handling of invalid keys, timeouts, and retries.

Where to go next

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