E-Mail-Verifizierung von Hunter oder Apollo zu Cold Leads migrieren
- Für wen
- Teams, die E-Mail-Adressen heute mit Hunter oder Apollo prüfen
- Das Problem
- Prüfcode steckt voller anbieterspezifischer Parameter und Statuswerte. Wer nur den Endpunkt austauscht, ohne sie abzubilden, ändert stillschweigend, an welche Adressen eine Pipeline sendet.
- Die Lösung
- Bilden Sie jeden Parameter und jedes Antwortfeld auf sein Gegenstück bei Cold Leads ab, lassen Sie, was Cold Leads nicht ersetzt (Personendaten, Domainsuche), wo es ist, und nutzen Sie einen kleinen Adapter, damit bestehender Code seine Form behält.
- Was Sie bekommen
- Eine Zuordnung Feld für Feld, ein Python-Adapter, der Felder im Hunter-Stil aus Cold Leads liefert, und eine Tabelle der Cold-Leads-Kosten für 1.000, 10.000 und 50.000 Prüfungen im Monat.
Adressen, IDs und Ergebnisse in den Beispielen sind illustrativ. example.com ist für Dokumentation reserviert, eine echte Prüfung dieser Adressen liefert daher invalid.
Die Codebeispiele sind in allen Sprachen gleich, ihre Kommentare sind auf Englisch.
Was umzieht und was bleibt
- Zieht um: Einzelprüfungen (Hunter Email Verifier) zu POST /api/v1/verify, Listen zu POST /api/v1/verify/bulk (bis zu 5.000 Adressen pro Auftrag) und Vermutungen aus Name und Domain (Hunter Email Finder) zu POST /api/v1/find.
- Bleibt: Hunters Domain Search samt den Webquellen hinter ihren Ergebnissen sowie Apollos People Search, People Enrichment und Bulk People Enrichment. Cold Leads hat keine Datenbank mit Personen oder Unternehmen; GET /api/v1/leads durchsucht nur die Kontakte in Ihrem eigenen Cold-Leads-CRM.
- Apollos öffentliche API hat keinen eigenständigen Prüf-Endpunkt; email_status kommt mit den Personendaten. Wenn Sie Apollo für Daten behalten, können Sie die gelieferten Adressen vor dem Versand mit Cold Leads prüfen.
Email Verifier zu POST /api/v1/verify
| Hunter | Cold Leads | Hinweis |
|---|---|---|
| GET /v2/email-verifier?email=… | POST /api/v1/verify mit einem JSON-Body {email} | Cold Leads erwartet einen POST mit JSON-Body. |
| Query-Parameter api_key, Header X-API-KEY oder Authorization: Bearer | Header x-api-key oder Authorization: Bearer | Schlüssel im Query-String werden nicht akzeptiert. |
| Kein Zeitparameter; 202, solange die Prüfung noch läuft | timeout_ms, 1.000 bis 30.000 | Cold Leads antwortet, wenn die Prüfung fertig oder das Budget aufgebraucht ist (dann risky, Grund timeout). |
| data.status valid | status valid mit dem letzten Grund ok | valid mit smtp_unreachable bedeutet, dass das Postfach nicht geprüft wurde. |
| data.status invalid | status invalid, Grund mailbox_missing, no_mx oder syntax | |
| data.status accept_all | status risky, Grund catch_all, catch_all true | |
| data.status disposable | disposable true (status risky oder invalid, wenn die Domain keinen MX hat) | Eingebaute Liste mit 40 Wegwerf-Mail-Domains. |
| data.status webmail | Keine Entsprechung | Webmail-Adressen werden wie alle anderen geprüft. |
| data.status unknown | status risky, Grund smtp_unknown oder timeout | |
| data.score | score (0 bis 100) | Andere Skala; entscheiden Sie nach den Gründen, nicht nach der Zahl. |
| data.regexp | reasons enthält syntax, wenn die Syntaxprüfung fehlschlägt | |
| data.mx_records | mx: der erste MX-Host oder null, wenn die Domain weder einen MX- noch einen A-Eintrag hat (Grund no_mx) | |
| data.smtp_server | smtp_unreachable, wenn kein Mailserver per SMTP erreichbar war | ok, catch_all und mailbox_missing bedeuten, dass ein Mailserver auf die Empfängerprüfung geantwortet hat. |
| data.smtp_check | letzter Grund ok oder catch_all | Ein Mailserver hat die Adresse akzeptiert. |
| data.accept_all | catch_all | Nur aussagekräftig, wenn ein Mailserver geantwortet hat (letzter Grund ok, catch_all oder mailbox_missing). |
| data.gibberish, data.block, data.sources | Keine Entsprechung |
Email Finder zu POST /api/v1/find
| Hunter | Cold Leads | Hinweis |
|---|---|---|
| domain | domain | Eine URL wird auf ihren Host reduziert (aus https://www.example.com/about wird example.com). |
| company | Keine Entsprechung | Eine Domain ist Pflicht. |
| first_name, last_name | first, last | Akzente werden entfernt und andere nicht lateinische Zeichen verworfen; transliterieren Sie Namen daher vorher. |
| full_name | name | Wird am ersten Leerzeichen in first und last aufgeteilt. |
| linkedin_handle, max_duration | Keine Entsprechung | |
| data.email | null, wenn kein Kandidat gebildet werden konnte, zum Beispiel wenn die Domain keinen MX hat. | |
| data.score | confidence (0 bis 100) | |
| data.verification.status | method | verified nur, wenn ein Mailserver das Postfach bestätigt hat; pattern für eine Vermutung aus gängigen Adressmustern; none, wenn nichts gefunden wurde. |
| data.accept_all | catch_all | |
| data.position, twitter, linkedin_url, phone_number, company, sources | Keine Entsprechung | |
| (kein Feld) | candidates | Aus gängigen Mustern gebildete Adressen, jeweils mit dem Status ihrer Prüfung, sofern sie geprüft wurden. |
Ein Drop-in-Adapter
Wenn Ihr Code nach Hunters Feldnamen verzweigt, liefern diese beiden Funktionen dieselbe Struktur aus Cold Leads, dazu die Rohantwort unter coldleads. Die Zuordnung ist bewusst vorsichtig: Eine Adresse, deren Postfach nicht geprüft wurde (smtp_unreachable), wird zu unknown, nicht zu valid.
# pip install httpx
# Drop-in helpers for code written against Hunter's email-verifier and email-finder.
# They call Cold Leads and return Hunter-style fields, plus the raw Cold Leads answer.
import os
import httpx
cold = httpx.Client(
base_url="https://coldleads.app/api/v1",
headers={"x-api-key": os.environ["COLDLEADS_API_KEY"]}, # the key goes in a header, never in the URL
timeout=40.0,
)
# a mail server answered the SMTP probe only when the last reason is one of these
PROBED = {"ok", "catch_all", "mailbox_missing"}
def email_verifier(email: str) -> dict:
r = cold.post("/verify", json={"email": email, "timeout_ms": 20000})
r.raise_for_status()
c = r.json()
last = c["reasons"][-1]
if c["disposable"] and last not in ("syntax", "no_mx"):
status = "disposable"
elif last == "ok":
status = "valid"
elif last == "catch_all":
status = "accept_all"
elif last in ("mailbox_missing", "no_mx", "syntax"):
status = "invalid"
else:
# smtp_unreachable (mailbox not checked), smtp_unknown or timeout: not verified
status = "unknown"
return {
"email": c["email"],
"status": status,
"score": c["score"],
"regexp": last != "syntax",
"disposable": c["disposable"],
"mx_records": c["mx"] is not None,
"smtp_check": last in ("ok", "catch_all"),
"accept_all": c["catch_all"] if last in PROBED else None, # None: not tested
"coldleads": c,
}
def email_finder(domain: str, first_name: str = "", last_name: str = "", full_name: str = "") -> dict:
body = {"domain": domain, "first": first_name, "last": last_name}
if full_name and not (first_name or last_name):
body = {"domain": domain, "name": full_name}
r = cold.post("/find", json=body)
r.raise_for_status()
c = r.json()
return {
"email": c["email"],
"score": c["confidence"],
"accept_all": c["catch_all"],
# Cold Leads says "verified" only when a mail server confirmed the mailbox; a pattern guess is not verified
"verification": {"status": "valid" if c["method"] == "verified" else None},
"coldleads": c,
}
if __name__ == "__main__":
print(email_verifier("anna@example.com"))Verhaltensunterschiede, die Sie einplanen sollten
- Fehlercodes: Cold Leads antwortet mit 429 bei seinem Ratenlimit (rate_limited, mit Retry-After: 60) und bei zu vielen offenen Bulk-Aufträgen (too_many_jobs) sowie mit 402 no_credits, wenn die Credits ausgehen. Hunter dokumentiert 403 für sein Ratenlimit und 429 für sein Nutzungslimit; die Fehlerbehandlung muss also neu geschrieben und nicht nur umbenannt werden.
- Ratenlimit: 120 Anfragen pro Minute und Schlüssel. Bei Listen ersetzt ein Bulk-Auftrag bis zu 5.000 Einzelaufrufe.
- Jeder Aufruf kostet 1 Credit, auch für Ergebnisse aus dem 30-Tage-Cache.
- Die SMTP-Prüfung von Postfach und Accept-all läuft nur, wenn Cold Leads eine SMTP-Verbindung zum Mailserver des Empfängers aufbauen kann. Die reasons jedes Ergebnisses zeigen, ob sie gelaufen ist: smtp_unreachable bedeutet, dass das Postfach nicht geprüft wurde.
Limits und Kosten
| Prüfungen im Monat | Credits | Cold-Leads-Kosten im Monat |
|---|---|---|
| 1.000 | 1.000 | $99: der Business-Tarif, der 10.000 Credits enthält |
| 10.000 | 10.000 | $99 |
| 50.000 | 50.000 | $299: $99 plus 40 zusätzliche Pakete zu 1.000 Credits für je $5 |
Die API gibt es nur im Business-Tarif, der auch jährlich bezahlt werden kann ($999). Aufrufe der Adresssuche verbrauchen dieselben Credits, 1 pro Aufruf. Monatliche Credits zählen pro Kalendermonat (UTC) und werden vor gekauften Paketen verbraucht. Preise in US-Dollar; es können Steuern anfallen.
FAQ
Gibt es bei Cold Leads ein Gegenstück zu Hunters Domain Search oder Apollos Personensuche?
Nein. Cold Leads sammelt keine Personendaten. GET /api/v1/leads durchsucht nur die Kontakte, die Sie bereits in Ihrem Cold-Leads-CRM führen.
Funktionieren meine bisherigen Score-Schwellen weiter?
Nicht zuverlässig, denn die Skalen unterscheiden sich. Entscheiden Sie stattdessen nach status und dem letzten Grundcode: ok zum Senden, catch_all und smtp_unknown zum manuellen Prüfen, mailbox_missing, no_mx und syntax zum Verwerfen, und smtp_unreachable für Adressen, deren Postfach nicht geprüft wurde.
Kann ich einen Apollo-Export mit Cold Leads prüfen?
Ja. Senden Sie die Adressen als Bulk-Aufträge mit bis zu 5.000 Adressen und fragen Sie die Ergebnisse ab, oder nutzen Sie das Python-Skript für CSV-Dateien auf dieser Website. Jede Adresse kostet 1 Credit.
Kosten zwischengespeicherte Ergebnisse weniger?
Nein. Ein Ergebnis aus dem 30-Tage-Cache ist mit cached: true gekennzeichnet und kostet 1 Credit wie jeder andere Aufruf.