Ce que vous allez apprendre
Ce guide couvre quatre scénarios d’intégration Python avec l’API Syvel :
- Script standalone : valider une adresse ou une liste depuis la ligne de commande
- FastAPI : validation asynchrone dans un endpoint d’inscription
- Django : validator custom pour les modèles et formulaires
- Batch processing : valider des milliers d’adresses de façon efficace
Prérequis : Python 3.9+, httpx pour les appels async (pip install httpx).
Votre clé API
Toutes les requêtes nécessitent votre clé API dans le header Authorization. Récupérez-la dans votre tableau de bord Syvel →. Consultez aussi la référence complète de l’endpoint /v1/check pour la liste exhaustive des champs retournés.
export SYVEL_API_KEY="sv_votre_cle_ici"Le format de votre clé commence toujours par sv_. Ne la commitez jamais dans votre code — utilisez les variables d’environnement.
Réponse API : les champs clés
L’endpoint GET /v1/check/{email} retourne :
{ "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}Les champs de décision principaux :
is_risky:truesi le score dépasse le seuil (défaut : 65). C’est le champ de blocage recommandé.risk_score(0–100) : pour un seuil personnalisé.reason:safe·disposable·undeliverable·role_account.did_you_mean: suggestion si typo détectée (gmial.com→gmail.com).deliverability_score(0–100) : probabilité de livraison effective.
Exemple 1 : Script standalone (synchrone)
Le moyen le plus rapide de tester l’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: """Valide un email via l'API Syvel. Retourne le résultat complet.""" 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 à valider : ") result = validate_email(email)
print(f"\n📧 Analyse de : {email}") print(f" Score de risque : {result['risk_score']}/100") print(f" Score délivrabilité : {result['deliverability_score']}/100") print(f" Risqué : {'⚠ Oui' if result['is_risky'] else '✓ Non'}") print(f" Raison : {result['reason']}") print(f" Provider MX : {result.get('mx_provider_label') or 'Inconnu'}")
if result.get('did_you_mean'): print(f" Vouliez-vous dire : {result['did_you_mean']}")
if __name__ == "__main__": main()
# Output :# 📧 Analyse de : [email protected]# Score de risque : 100/100# Score délivrabilité : 0/100# Risqué : ⚠ Oui# Raison : disposable# Provider MX : YopmailExemple 2 : FastAPI (asynchrone)
L’intégration idéale pour une API REST moderne. L’appel async ne bloque pas le thread principal :
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: """ Vérifie la qualité d'un email via Syvel. Retourne None en cas d'erreur (fail open — ne jamais bloquer sur erreur externe). """ if not SYVEL_API_KEY: logger.warning("SYVEL_API_KEY non configurée, validation ignorée") 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 pour {email}") except Exception as e: logger.error(f"Syvel erreur inattendue : {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: # Bloquer les emails risqués (jetables, non livrables) if quality.is_risky: detail = { "field": "email", "code": f"email_{quality.reason}", } if quality.reason == "undeliverable": detail["message"] = "Ce domaine ne peut pas recevoir d'emails." elif quality.reason == "role_account": detail["message"] = "Veuillez utiliser une adresse email personnelle (pas admin@, info@…)." else: detail["message"] = "Cette adresse email n'est pas acceptée."
# Suggérer une correction si typo détectée if quality.did_you_mean: detail["did_you_mean"] = quality.did_you_mean
raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail, )
# Ici : créer l'utilisateur en base, hasher le mot de passe, etc. return {"status": "created", "email": body.email}Bonnes pratiques FastAPI :
- Réutilisez
httpx.AsyncClientvia un singleton ou l’injection de dépendances — créer un nouveau client par requête est inefficace. - Définissez un timeout court (5 s max) — un service tiers lent ne doit pas bloquer vos utilisateurs.
- Loggez les erreurs mais ne bloquez pas —
fail openest la règle pour les validations externes.
Partage du client HTTP avec le 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)
# Dans votre router, injectez via dépendance :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) ...Exemple 3 : Django
Pour Django, créez un validator personnalisé utilisable dans les modèles et formulaires :
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 — lève ValidationError si l'email est inacceptable. Fail open en cas d'erreur externe. """ if not SYVEL_API_KEY: return # Validation désactivée si clé non configurée
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( "Ce domaine ne peut pas recevoir d'emails.", code="email_undeliverable", ) elif reason == "role_account": raise ValidationError( "Veuillez utiliser une adresse email personnelle.", code="email_role_account", ) else: raise ValidationError( "Cette adresse email n'est pas acceptée. Veuillez utiliser votre email professionnel.", 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], # S'applique à full_clean() et les formulaires ) 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)Exemple 4 : Traitement batch
Pour valider des milliers d’adresses (import de liste, purge CRM) :
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 # Nombre de requêtes simultanées
@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 — attendre et réessayer 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"🔍 Validation de {len(emails)} adresses...") 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]
# Écrire les résultats 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"✅ Valides : {len(valid)}") print(f"🚫 Bloquées : {len(blocked)}") print(f"⚠️ Erreurs : {len(errors)}") print(f"\nFichiers générés : {output_valid}, {output_blocked}, {output_errors}")
if __name__ == "__main__": main()Utilisation :
# Format emails.csv : une colonne "email"python batch_validate.py# Validation de 5000 adresses...# ✅ Valides : 4231# 🚫 Bloquées : 623# ⚠️ Erreurs : 146Performance : avec CONCURRENCY = 10, 5 000 adresses sont traitées rapidement. Augmentez la concurrence selon votre plan Syvel (vérifiez votre quota de requêtes/minute dans le dashboard).
Résumé des patterns recommandés
| Contexte | Pattern | Points clés |
|---|---|---|
| Script CLI | httpx.Client synchrone | Simple, direct |
| FastAPI | httpx.AsyncClient partagé via lifespan | Async, réutilisation client |
| Django | Validator synchrone | Compatible modèles + formulaires |
| Batch | asyncio.gather + Semaphore | Concurrence contrôlée, gestion 429 |
Dans tous les cas : fail open (ne pas bloquer si l’API est indisponible), timeout court (5–10 s max), log des erreurs sans les propager à l’utilisateur.
Pour compléter votre stack, consultez aussi notre guide d’intégration Python dans la documentation officielle et le guide équivalent pour React / TypeScript. Pour comprendre quand utiliser chaque méthode de validation, lisez Regex, DNS, SMTP : quelle méthode choisir ?. Et pour les volumes importants, comparez les options tarifaires.