Cold Leads

=VERIFY_EMAIL() y =FIND_EMAIL() en Google Sheets

Para quién
Quienes guardan listas de leads en Google Sheets
El problema
Exportar una hoja a una herramienta de verificación y volver a pegar los resultados es tedioso. Una función personalizada ingenua vuelve a llamar a la API, y vuelve a gastar un crédito, cada vez que Sheets ejecuta de nuevo la fórmula.
La solución
Dos funciones personalizadas llaman a la API de Cold Leads con UrlFetchApp, leen la clave de las propiedades del script y guardan cada respuesta en la caché del script hasta seis horas, así que volver a ejecutar la misma fórmula no gasta otro crédito con la misma entrada.
Qué obtiene
Tres celdas de resultado por fila: estado, puntuación y motivo con =VERIFY_EMAIL, o dirección, método y confianza con =FIND_EMAIL.

Las direcciones, los identificadores y los resultados de los ejemplos son ilustrativos. example.com está reservado para documentación, así que una comprobación real de estas direcciones devuelve invalid.

Los ejemplos de código son iguales en todos los idiomas; sus comentarios están en inglés.

Configúrelo

  1. En la hoja de cálculo abra Extensions → Apps Script y sustituya el contenido de Code.gs por el script de abajo. Guarde.
  2. Haga clic en Project Settings y después, en Script Properties, en Add script property. Property: COLDLEADS_API_KEY, value: su clave secreta de Cold Leads (sk_…). Haga clic en Save script properties.
  3. De vuelta en la hoja, escriba =VERIFY_EMAIL(A2) en B2. El resultado ocupa B2, C2 y D2 (estado, puntuación, motivo), así que deje vacías esas dos celdas de la derecha. Extienda la fórmula hacia abajo.
  4. Para las direcciones que aún no tiene, escriba =FIND_EMAIL(B2, C2, D2) con nombre, apellido y dominio o web de la empresa. Rellena la dirección, el método y la confianza.

El script

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;
}

Cómo se comporta

  • Sheets vuelve a ejecutar una función personalizada cuando usted edita la fórmula, la copia en otra celda o cambia una celda a la que hace referencia. Sin caché, cada ejecución es una nueva llamada a la API y un nuevo crédito.
  • La caché del script guarda cada respuesta hasta 6 horas y la comparten todos los que usan el script. Google puede descartar entradas antes si la caché está llena, y advierte que el servicio Cache funciona en funciones personalizadas pero que allí «no es especialmente útil»; aquí solo evita pagar dos veces por la misma entrada.
  • Las respuestas con el motivo timeout no se guardan en caché, así que la siguiente ejecución vuelve a comprobar la dirección.
  • Una función personalizada debe terminar en 30 segundos. =VERIFY_EMAIL pide a Cold Leads un margen de 20 segundos. =FIND_EMAIL no tiene margen y puede superar los 30 segundos en un dominio donde se comprueban varias direcciones candidatas; la celda muestra entonces un error, y el crédito ya se ha gastado.
  • Cualquiera que pueda editar la hoja de cálculo puede abrir su proyecto de Apps Script y, con él, la clave. Dé acceso de edición solo a quienes puedan usar la clave, y regenere la clave en Cold Leads (Ajustes → Claves de API) si eso cambia.

Cómo leer los resultados

FunciónResultadoSignificado
VERIFY_EMAILvalid, 97 u 80, okUn servidor de correo aceptó el buzón (80 para direcciones de rol como info@).
VERIFY_EMAILvalid, 75 o 65, smtp_unreachableEl dominio acepta correo, pero el propio buzón no se comprobó.
VERIFY_EMAILrisky, catch_all o smtp_unknownEl servidor acepta cualquier dirección o no dio una respuesta concluyente.
VERIFY_EMAILrisky, timeoutLa comprobación se quedó sin tiempo; se repite la próxima vez.
VERIFY_EMAILinvalid, mailbox_missing, no_mx o syntaxNo use la dirección.
FIND_EMAILdirección, verifiedUn servidor de correo confirmó el buzón.
FIND_EMAILdirección, patternUna suposición basada en formatos de dirección habituales; confírmela antes de usarla.
FIND_EMAILvacío, noneNingún candidato: el dominio no tiene servidor de correo o no se pudo usar el nombre.

Límites y costes

  • 1 crédito de Cold Leads por cada llamada que no se responde desde la caché del script. Cold Leads también cobra los resultados de su propia caché de 30 días, así que lo que ahorra créditos es la caché del script.
  • Apps Script admite 20 000 llamadas URL Fetch al día en cuentas personales y 100 000 en Google Workspace, y 30 segundos por llamada a una función personalizada. Rellenar una fórmula en miles de filas a la vez puede fallar con Script invoked too many times per second; rellene en bloques más pequeños o use una tarea masiva para listas grandes.
  • Cold Leads admite 120 peticiones por minuto por clave. La API forma parte del plan Business: $99 al mes con 10 000 créditos al mes; los paquetes extra de 1 000 créditos cuestan $5.

Preguntas frecuentes

La celda muestra #ERROR!. ¿Qué ha pasado?

Pase el cursor por encima de la celda para leer el mensaje: la ausencia de la propiedad COLDLEADS_API_KEY, un error de Cold Leads como no_credits o rate_limited, o el límite de 30 segundos de las funciones personalizadas.

¿Volver a abrir la hoja de cálculo cuesta créditos?

Sheets vuelve a ejecutar una función personalizada sobre todo cuando cambian su fórmula o sus celdas de entrada. Si se vuelve a ejecutar con la misma entrada dentro de seis horas, responde la caché del script sin llamar a Cold Leads.

¿Cómo compruebo miles de direcciones?

Use una tarea masiva en lugar de una función personalizada: hasta 5 000 direcciones por petición, con sondeo hasta que termine. Las páginas de n8n y Python de este sitio muestran versiones completas.