Cold Leads

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

HunterCold LeadsHinweis
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: BearerHeader x-api-key oder Authorization: BearerSchlüssel im Query-String werden nicht akzeptiert.
Kein Zeitparameter; 202, solange die Prüfung noch läufttimeout_ms, 1.000 bis 30.000Cold Leads antwortet, wenn die Prüfung fertig oder das Budget aufgebraucht ist (dann risky, Grund timeout).
data.status validstatus valid mit dem letzten Grund okvalid mit smtp_unreachable bedeutet, dass das Postfach nicht geprüft wurde.
data.status invalidstatus invalid, Grund mailbox_missing, no_mx oder syntax
data.status accept_allstatus risky, Grund catch_all, catch_all true
data.status disposabledisposable true (status risky oder invalid, wenn die Domain keinen MX hat)Eingebaute Liste mit 40 Wegwerf-Mail-Domains.
data.status webmailKeine EntsprechungWebmail-Adressen werden wie alle anderen geprüft.
data.status unknownstatus risky, Grund smtp_unknown oder timeout
data.scorescore (0 bis 100)Andere Skala; entscheiden Sie nach den Gründen, nicht nach der Zahl.
data.regexpreasons enthält syntax, wenn die Syntaxprüfung fehlschlägt
data.mx_recordsmx: der erste MX-Host oder null, wenn die Domain weder einen MX- noch einen A-Eintrag hat (Grund no_mx)
data.smtp_serversmtp_unreachable, wenn kein Mailserver per SMTP erreichbar warok, catch_all und mailbox_missing bedeuten, dass ein Mailserver auf die Empfängerprüfung geantwortet hat.
data.smtp_checkletzter Grund ok oder catch_allEin Mailserver hat die Adresse akzeptiert.
data.accept_allcatch_allNur aussagekräftig, wenn ein Mailserver geantwortet hat (letzter Grund ok, catch_all oder mailbox_missing).
data.gibberish, data.block, data.sourcesKeine Entsprechung

Email Finder zu POST /api/v1/find

HunterCold LeadsHinweis
domaindomainEine URL wird auf ihren Host reduziert (aus https://www.example.com/about wird example.com).
companyKeine EntsprechungEine Domain ist Pflicht.
first_name, last_namefirst, lastAkzente werden entfernt und andere nicht lateinische Zeichen verworfen; transliterieren Sie Namen daher vorher.
full_namenameWird am ersten Leerzeichen in first und last aufgeteilt.
linkedin_handle, max_durationKeine Entsprechung
data.emailemailnull, wenn kein Kandidat gebildet werden konnte, zum Beispiel wenn die Domain keinen MX hat.
data.scoreconfidence (0 bis 100)
data.verification.statusmethodverified 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_allcatch_all
data.position, twitter, linkedin_url, phone_number, company, sourcesKeine Entsprechung
(kein Feld)candidatesAus 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.

hunter_adapter.py
# 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 MonatCreditsCold-Leads-Kosten im Monat
1.0001.000$99: der Business-Tarif, der 10.000 Credits enthält
10.00010.000$99
50.00050.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.