Cold Leads

=VERIFY_EMAIL() und =FIND_EMAIL() in Google Sheets

Für wen
Alle, die Lead-Listen in Google Sheets führen
Das Problem
Ein Sheet in ein Prüf-Tool zu exportieren und die Ergebnisse zurückzukopieren ist mühsam. Eine naive benutzerdefinierte Funktion ruft die API jedes Mal erneut auf und verbraucht erneut einen Credit, wenn Sheets die Formel erneut ausführt.
Die Lösung
Zwei benutzerdefinierte Funktionen rufen die Cold-Leads-API mit UrlFetchApp auf, lesen den Schlüssel aus den Skripteigenschaften (Script Properties) und halten jede Antwort bis zu sechs Stunden im Skript-Cache; eine erneute Ausführung derselben Formel verbraucht für dieselbe Eingabe also keinen weiteren Credit.
Was Sie bekommen
Drei Ergebniszellen pro Zeile: Status, Score und Grund aus =VERIFY_EMAIL oder Adresse, Methode und Konfidenz aus =FIND_EMAIL.

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.

Einrichten

  1. Öffnen Sie in der Tabelle Extensions → Apps Script und ersetzen Sie den Inhalt von Code.gs durch das Skript unten. Speichern Sie.
  2. Klicken Sie auf Project Settings und dann unter Script Properties auf Add script property. Property: COLDLEADS_API_KEY, Value: Ihr geheimer Cold-Leads-Schlüssel (sk_…). Klicken Sie auf Save script properties.
  3. Geben Sie zurück im Sheet in B2 =VERIFY_EMAIL(A2) ein. Das Ergebnis füllt B2, C2 und D2 (Status, Score, Grund), lassen Sie die beiden Zellen rechts daneben also leer. Ziehen Sie die Formel nach unten.
  4. Für Adressen, die Sie noch nicht haben, geben Sie =FIND_EMAIL(B2, C2, D2) mit Vorname, Nachname und Firmendomain oder -website ein. Die Formel füllt Adresse, Methode und Konfidenz.

Das Skript

Code.gs
// Cold Leads custom functions for Google Sheets (Extensions → Apps Script, paste into Code.gs).
// Key: Project Settings → Script Properties → Add script property COLDLEADS_API_KEY = sk_...
const COLDLEADS_API = 'https://coldleads.app/api/v1';
const CACHE_SECONDS = 21600; // 6 hours, the longest CacheService keeps an entry

/**
 * Verifies an e-mail address with Cold Leads. Fills three cells: status, score, last reason code.
 * Costs 1 Cold Leads credit unless the same address was answered from this script's cache.
 *
 * @param {string} email The address, for example A2.
 * @return Status, score and reason.
 * @customfunction
 */
function VERIFY_EMAIL(email) {
  const address = String(email || '').trim().toLowerCase();
  if (!address) return '';
  const r = cachedCall_('verify:' + address, '/verify', { email: address, timeout_ms: 20000 }, function (res) {
    return res.reasons[res.reasons.length - 1] !== 'timeout'; // timeouts are worth checking again
  });
  return [[r.status, r.score, r.reasons[r.reasons.length - 1]]];
}

/**
 * Finds the most likely e-mail address for a person at a company domain. Fills three cells:
 * address, method ("verified" only when a mail server confirmed the mailbox, otherwise "pattern"), confidence.
 * Costs 1 Cold Leads credit unless answered from this script's cache.
 *
 * @param {string} first First name, for example B2.
 * @param {string} last Last name, for example C2.
 * @param {string} domain Company domain or website, for example D2.
 * @return Address, method and confidence.
 * @customfunction
 */
function FIND_EMAIL(first, last, domain) {
  const body = { first: String(first || '').trim(), last: String(last || '').trim(), domain: String(domain || '').trim().toLowerCase() };
  if (!body.domain || !(body.first || body.last)) return '';
  const key = 'find:' + [body.first, body.last, body.domain].join('|').toLowerCase();
  const r = cachedCall_(key, '/find', body, function () { return true; });
  return [[r.email || '', r.method, r.confidence]];
}

function cachedCall_(cacheKey, path, payload, cacheable) {
  const cache = CacheService.getScriptCache();
  // cache keys are limited to 250 characters, so store a hash of the key
  const id = Utilities.base64Encode(Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, cacheKey, Utilities.Charset.UTF_8));
  const hit = cache.get(id);
  if (hit) return JSON.parse(hit);
  const apiKey = PropertiesService.getScriptProperties().getProperty('COLDLEADS_API_KEY');
  if (!apiKey) throw new Error('Add the script property COLDLEADS_API_KEY (Project Settings → Script Properties).');
  const res = UrlFetchApp.fetch(COLDLEADS_API + path, {
    method: 'post',
    contentType: 'application/json',
    headers: { 'x-api-key': apiKey },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true,
  });
  const data = JSON.parse(res.getContentText() || '{}');
  if (res.getResponseCode() !== 200) throw new Error('Cold Leads: ' + (data.error || 'HTTP ' + res.getResponseCode()));
  if (cacheable(data)) cache.put(id, JSON.stringify(data), CACHE_SECONDS);
  return data;
}

Wie es sich verhält

  • Sheets führt eine benutzerdefinierte Funktion erneut aus, wenn Sie die Formel bearbeiten, in eine andere Zelle kopieren oder eine Zelle ändern, auf die sie verweist. Ohne Cache ist jede Ausführung ein neuer API-Aufruf und ein neuer Credit.
  • Der Skript-Cache hält jede Antwort bis zu 6 Stunden und wird von allen geteilt, die das Skript nutzen. Google kann Einträge früher verwerfen, wenn der Cache voll ist, und weist darauf hin, dass der Cache Service in benutzerdefinierten Funktionen zwar funktioniert, dort aber „nicht besonders nützlich“ ist; hier verhindert er nur, dass Sie für dieselbe Eingabe doppelt bezahlen.
  • Antworten mit dem Grund timeout werden nicht zwischengespeichert, die nächste Ausführung prüft die Adresse also erneut.
  • Eine benutzerdefinierte Funktion muss innerhalb von 30 Sekunden fertig sein. =VERIFY_EMAIL fordert bei Cold Leads ein Budget von 20 Sekunden an. =FIND_EMAIL hat kein Budget und kann bei einer Domain, bei der mehrere Kandidatenadressen geprüft werden, 30 Sekunden überschreiten; die Zelle zeigt dann einen Fehler, und der Credit ist bereits verbraucht.
  • Wer die Tabelle bearbeiten kann, kann auch ihr Apps-Script-Projekt öffnen und damit den Schlüssel sehen. Geben Sie Bearbeitungsrechte nur Personen, die den Schlüssel nutzen dürfen, und erneuern Sie den Schlüssel in Cold Leads (Einstellungen → API-Schlüssel), wenn sich das ändert.

Die Ergebnisse lesen

FunktionErgebnisBedeutung
VERIFY_EMAILvalid, 97 oder 80, okEin Mailserver hat das Postfach akzeptiert (80 bei Rollenadressen wie info@).
VERIFY_EMAILvalid, 75 oder 65, smtp_unreachableDie Domain nimmt E-Mails an, aber das Postfach selbst wurde nicht geprüft.
VERIFY_EMAILrisky, catch_all oder smtp_unknownDer Server akzeptiert jede Adresse oder hat keine eindeutige Antwort gegeben.
VERIFY_EMAILrisky, timeoutDer Prüfung ist die Zeit ausgegangen; beim nächsten Mal läuft sie erneut.
VERIFY_EMAILinvalid, mailbox_missing, no_mx oder syntaxAdresse nicht verwenden.
FIND_EMAILAdresse, verifiedEin Mailserver hat das Postfach bestätigt.
FIND_EMAILAdresse, patternEine Vermutung aus gängigen Adressformaten; vor der Verwendung bestätigen.
FIND_EMAILleer, noneKein Kandidat: Die Domain hat keinen Mailserver, oder der Name war nicht verwendbar.

Limits und Kosten

  • 1 Cold-Leads-Credit pro Aufruf, der nicht aus dem Skript-Cache beantwortet wird. Cold Leads berechnet auch Ergebnisse aus seinem eigenen 30-Tage-Cache; Credits spart also der Skript-Cache.
  • Apps Script erlaubt 20.000 URL-Fetch-Aufrufe pro Tag bei privaten Konten und 100.000 bei Google Workspace sowie 30 Sekunden pro Aufruf einer benutzerdefinierten Funktion. Eine Formel auf einmal in Tausende Zeilen zu füllen, kann mit Script invoked too many times per second fehlschlagen; füllen Sie in kleineren Blöcken aus oder nutzen Sie für große Listen einen Bulk-Auftrag.
  • Cold Leads erlaubt 120 Anfragen pro Minute und Schlüssel. Die API ist Teil des Business-Tarifs: $99 im Monat mit 10.000 Credits im Monat; zusätzliche Pakete zu 1.000 Credits kosten $5.

FAQ

Die Zelle zeigt #ERROR!. Was ist passiert?

Fahren Sie mit der Maus über die Zelle, um die Meldung zu lesen: eine fehlende Eigenschaft COLDLEADS_API_KEY, ein Cold-Leads-Fehler wie no_credits oder rate_limited oder das 30-Sekunden-Limit benutzerdefinierter Funktionen.

Kostet das erneute Öffnen der Tabelle Credits?

Sheets führt eine benutzerdefinierte Funktion vor allem dann erneut aus, wenn sich ihre Formel oder ihre Eingabezellen ändern. Läuft sie doch innerhalb von sechs Stunden mit derselben Eingabe erneut, antwortet der Skript-Cache, ohne Cold Leads aufzurufen.

Wie prüfe ich Tausende Adressen?

Nutzen Sie statt einer benutzerdefinierten Funktion einen Bulk-Auftrag: bis zu 5.000 Adressen pro Anfrage, abgefragt bis zum Abschluss. Die Seiten zu n8n und Python auf dieser Website zeigen vollständige Versionen.