What You’ll Learn
This guide covers four Python integration scenarios with the Syvel API:
- Standalone script: validate an address or list from the command line
- FastAPI: asynchronous validation in a signup endpoint
- Django: custom validator for models and forms
- Batch processing: validate thousands of addresses efficiently
Prerequisites: Python 3.9+, httpx for async calls (pip install httpx).
Your API Key
All requests require your API key in the Authorization header. Retrieve it from your Syvel dashboard →.
export SYVEL_API_KEY="sv_your_key_here"Your key format always starts with sv_. Never commit it to your code — use environment variables.
API Response: Key Fields
The GET /v1/check/{email} endpoint returns:
{ "is_risky": true, "risk_score": 100, "reason": "disposable", "is_free_provider": false, "is_corporate_email": false, "did_you_mean": null, "is_alias_email": false, "mx_provider_label": "Yopmail", "deliverability_score": 0}Primary decision fields:
is_risky:trueif score exceeds threshold (default: 65). This is the recommended blocking field.risk_score(0–100): for a custom threshold.reason:safe·disposable·undeliverable·role_account.did_you_mean: suggestion if typo detected (gmial.com→gmail.com).deliverability_score(0–100): probability of actual delivery.
Example 1: Standalone Script (Synchronous)
The fastest way to test the API:
import httpximport osimport sys
API_KEY = os.environ.get("SYVEL_API_KEY")BASE_URL = "https://api.syvel.io/v1"
def validate_email(email: str) -> dict: """Validates an email via the Syvel API. Returns the complete result.""" with httpx.Client(timeout=10.0) as client: response = client.get( f"{BASE_URL}/check/{email}", headers={"Authorization": f"Bearer {API_KEY}"}, ) response.raise_for_status() return response.json()
def main(): email = sys.argv[1] if len(sys.argv) > 1 else input("Email to validate: ") result = validate_email(email)
print(f"\n📧 Analysis of: {email}") print(f" Risk score : {result['risk_score']}/100") print(f" Deliverability : {result['deliverability_score']}/100") print(f" Risky : {'⚠ Yes' if result['is_risky'] else '✓ No'}") print(f" Reason : {result['reason']}") print(f" MX Provider : {result.get('mx_provider_label') or 'Unknown'}")
if result.get('did_you_mean'): print(f" Did you mean : {result['did_you_mean']}")
if __name__ == "__main__": main()
# Output:# 📧 Analysis of: [email protected]# Risk score : 100/100# Deliverability : 0/100# Risky : ⚠ Yes# Reason : disposable# MX Provider : YopmailExample 2: FastAPI (Asynchronous)
The ideal integration for a modern REST API. The async call doesn’t block the main thread:
import httpximport osimport loggingfrom fastapi import APIRouter, HTTPException, statusfrom pydantic import BaseModel, EmailStr
router = APIRouter()logger = logging.getLogger(__name__)
SYVEL_API_KEY = os.environ.get("SYVEL_API_KEY")SYVEL_BASE_URL = "https://api.syvel.io/v1/check"
class SyvelResult(BaseModel): risk_score: int is_risky: bool reason: str # "safe" | "disposable" | "undeliverable" | "role_account" did_you_mean: str | None = None deliverability_score: int
async def check_email_quality(email: str, client: httpx.AsyncClient) -> SyvelResult | None: """ Checks email quality via Syvel. Returns None on error (fail open — never block on external error). """ if not SYVEL_API_KEY: logger.warning("SYVEL_API_KEY not configured, validation skipped") return None try: response = await client.get( f"{SYVEL_BASE_URL}/{email}", headers={"Authorization": f"Bearer {SYVEL_API_KEY}"}, timeout=5.0, ) if response.status_code == 200: return SyvelResult(**response.json()) except httpx.TimeoutException: logger.warning(f"Syvel timeout for {email}") except Exception as e: logger.error(f"Syvel unexpected error: {e}") return None
class RegisterRequest(BaseModel): email: EmailStr password: str name: str
@router.post("/register", status_code=status.HTTP_201_CREATED)async def register(body: RegisterRequest): async with httpx.AsyncClient() as client: quality = await check_email_quality(body.email, client)
if quality is not None: # Block risky emails (disposable, undeliverable) if quality.is_risky: detail = { "field": "email", "code": f"email_{quality.reason}", } if quality.reason == "undeliverable": detail["message"] = "This domain cannot receive emails." elif quality.reason == "role_account": detail["message"] = "Please use a personal email address (not admin@, info@…)." else: detail["message"] = "This email address is not accepted."
# Suggest a correction if typo detected if quality.did_you_mean: detail["did_you_mean"] = quality.did_you_mean
raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail, )
# Here: create user in database, hash password, etc. return {"status": "created", "email": body.email}FastAPI best practices:
- Reuse
httpx.AsyncClientvia a singleton or dependency injection — creating a new client per request is inefficient. - Set a short timeout (5 s max) — a slow third-party service shouldn’t block your users.
- Log errors but don’t block —
fail openis the rule for external validations.
Sharing the HTTP Client with Lifespan
from contextlib import asynccontextmanagerimport httpxfrom fastapi import FastAPI
http_client: httpx.AsyncClient | None = None
@asynccontextmanagerasync def lifespan(app: FastAPI): global http_client http_client = httpx.AsyncClient(timeout=5.0) yield await http_client.aclose()
app = FastAPI(lifespan=lifespan)
# In your router, inject via dependency:from fastapi import Depends
def get_http_client() -> httpx.AsyncClient: return http_client
@router.post("/register")async def register(body: RegisterRequest, client: httpx.AsyncClient = Depends(get_http_client)): quality = await check_email_quality(body.email, client) ...Example 3: Django
For Django, create a custom validator usable in models and forms:
import httpximport osimport loggingfrom django.core.exceptions import ValidationError
logger = logging.getLogger(__name__)
SYVEL_API_KEY = os.environ.get("SYVEL_API_KEY")SYVEL_BASE_URL = "https://api.syvel.io/v1/check"
def validate_email_quality(email: str) -> None: """ Django validator — raises ValidationError if the email is unacceptable. Fail open on external error. """ if not SYVEL_API_KEY: return # Validation disabled if key not configured
try: with httpx.Client(timeout=5.0) as client: response = client.get( f"{SYVEL_BASE_URL}/{email}", headers={"Authorization": f"Bearer {SYVEL_API_KEY}"}, ) if response.status_code != 200: return # Fail open data = response.json() except Exception as e: logger.error(f"Syvel validation error for {email}: {e}") return # Fail open
if data.get("is_risky"): reason = data.get("reason", "disposable") if reason == "undeliverable": raise ValidationError( "This domain cannot receive emails.", code="email_undeliverable", ) elif reason == "role_account": raise ValidationError( "Please use a personal email address.", code="email_role_account", ) else: raise ValidationError( "This email address is not accepted. Please use your professional email.", code="email_disposable", )from django.db import modelsfrom django.core.validators import validate_emailfrom .validators import validate_email_quality
class UserProfile(models.Model): email = models.EmailField( unique=True, validators=[validate_email_quality], # Applies to full_clean() and forms ) name = models.CharField(max_length=255) created_at = models.DateTimeField(auto_now_add=True)from django import formsfrom .validators import validate_email_quality
class RegistrationForm(forms.Form): name = forms.CharField(max_length=255) email = forms.EmailField(validators=[validate_email_quality]) password = forms.CharField(widget=forms.PasswordInput)Example 4: Batch Processing
To validate thousands of addresses (list import, CRM purge):
import asyncioimport csvimport httpximport osimport loggingfrom dataclasses import dataclass, asdictfrom pathlib import Path
logger = logging.getLogger(__name__)
SYVEL_API_KEY = os.environ.get("SYVEL_API_KEY")CONCURRENCY = 10 # Number of simultaneous requests
@dataclassclass ValidationResult: email: str risk_score: int is_risky: bool reason: str deliverability_score: int did_you_mean: str error: str = ""
async def validate_one(email: str, client: httpx.AsyncClient, semaphore: asyncio.Semaphore) -> ValidationResult: async with semaphore: try: r = await client.get( f"https://api.syvel.io/v1/check/{email}", headers={"Authorization": f"Bearer {SYVEL_API_KEY}"}, timeout=10.0, ) if r.status_code == 200: d = r.json() return ValidationResult( email=email, risk_score=d["risk_score"], is_risky=d["is_risky"], reason=d["reason"], deliverability_score=d["deliverability_score"], did_you_mean=d.get("did_you_mean") or "", ) elif r.status_code == 429: # Rate limit — wait and retry await asyncio.sleep(2) return await validate_one(email, client, semaphore) except Exception as e: logger.error(f"Error validating {email}: {e}") return ValidationResult( email=email, risk_score=-1, is_risky=False, reason="error", deliverability_score=-1, did_you_mean="", error="API error", )
async def validate_batch(emails: list[str]) -> list[ValidationResult]: semaphore = asyncio.Semaphore(CONCURRENCY) async with httpx.AsyncClient() as client: tasks = [validate_one(email, client, semaphore) for email in emails] results = await asyncio.gather(*tasks) return list(results)
def main(): input_file = Path("emails.csv") output_valid = Path("emails_valid.csv") output_blocked = Path("emails_blocked.csv") output_errors = Path("emails_errors.csv")
with open(input_file) as f: reader = csv.DictReader(f) emails = [row["email"].strip() for row in reader if row.get("email")]
print(f"🔍 Validating {len(emails)} addresses...") results = asyncio.run(validate_batch(emails))
valid = [r for r in results if not r.is_risky and r.risk_score >= 0] blocked = [r for r in results if r.is_risky] errors = [r for r in results if r.risk_score < 0]
# Write results fields = ["email", "risk_score", "is_risky", "reason", "deliverability_score", "did_you_mean", "error"] for path, data in [(output_valid, valid), (output_blocked, blocked), (output_errors, errors)]: with open(path, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows([asdict(r) for r in data])
print(f"✅ Valid: {len(valid)}") print(f"🚫 Blocked: {len(blocked)}") print(f"⚠️ Errors: {len(errors)}") print(f"\nGenerated files: {output_valid}, {output_blocked}, {output_errors}")
if __name__ == "__main__": main()Usage:
# Format emails.csv: one "email" columnpython batch_validate.py# Validating 5000 addresses...# ✅ Valid: 4231# 🚫 Blocked: 623# ⚠️ Errors: 146Performance: with CONCURRENCY = 10, 5,000 addresses are processed quickly. Increase concurrency according to your Syvel plan (check your requests/minute quota in the dashboard).
Recommended Pattern Summary
| Context | Pattern | Key Points |
|---|---|---|
| CLI script | Synchronous httpx.Client | Simple, direct |
| FastAPI | Shared httpx.AsyncClient via lifespan | Async, client reuse |
| Django | Synchronous validator | Compatible with models + forms |
| Batch | asyncio.gather + Semaphore | Controlled concurrency, 429 handling |
In all cases: fail open (don’t block if API is unavailable), short timeout (5–10 s max), log errors without propagating them to the user.
For a React/TypeScript integration instead, see integrating Syvel with React Hook Form. And to understand which validation method is right for your use case, read regex vs DNS vs SMTP: which email validation method to choose.