Cold Leads

=VERIFY_EMAIL() і =FIND_EMAIL() у Google Sheets

Для кого
Ті, хто веде списки лідів у Google Sheets
Проблема
Експортувати аркуш в інструмент перевірки й вставляти результати назад — клопітно. Наївна користувацька функція знову викликає API і знову витрачає кредит щоразу, коли Sheets повторно виконує формулу.
Рішення
Дві користувацькі функції викликають API Cold Leads через UrlFetchApp, читають ключ із властивостей скрипту й зберігають кожну відповідь у кеші скрипту до шести годин, тож повторне виконання тієї самої формули не витрачає ще один кредит на ті самі вхідні дані.
Що ви отримаєте
Три клітинки з результатом у кожному рядку: статус, оцінка й причина від =VERIFY_EMAIL або адреса, метод і впевненість від =FIND_EMAIL.

Адреси, ідентифікатори та результати в прикладах ілюстративні. Домен example.com зарезервовано для документації, тож реальна перевірка цих адрес поверне invalid.

Приклади коду однакові для всіх мов, коментарі в них — англійською.

Налаштування

  1. У таблиці відкрийте Extensions → Apps Script і замініть вміст Code.gs скриптом нижче. Збережіть.
  2. Натисніть Project Settings, потім у розділі Script Properties натисніть Add script property. Property: COLDLEADS_API_KEY, значення (Value): ваш секретний ключ Cold Leads (sk_…). Натисніть Save script properties.
  3. Поверніться в аркуш і введіть =VERIFY_EMAIL(A2) у B2. Результат заповнить B2, C2 і D2 (статус, оцінка, причина), тож залиште ці дві клітинки праворуч порожніми. Протягніть формулу вниз.
  4. Для адрес, яких у вас ще немає, введіть =FIND_EMAIL(B2, C2, D2), де B2, C2 і D2 містять ім’я, прізвище та домен або сайт компанії. Функція заповнить адресу, метод і впевненість.

Скрипт

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

Як це працює

  • Sheets повторно виконує користувацьку функцію, коли ви редагуєте формулу, копіюєте її в іншу клітинку або змінюєте клітинку, на яку вона посилається. Без кешу кожне виконання — це новий виклик API і новий кредит.
  • Кеш скрипту зберігає кожну відповідь до 6 годин і спільний для всіх, хто користується скриптом. Google може видаляти записи раніше, коли кеш заповнений, і зазначає, що Cache service працює в користувацьких функціях, але там «не надто корисний»; тут він лише запобігає подвійній оплаті за ті самі вхідні дані.
  • Відповіді з причиною timeout не кешуються, тож наступне виконання перевірить адресу знову.
  • Користувацька функція має завершитися за 30 секунд. =VERIFY_EMAIL просить у Cold Leads 20 секунд. =FIND_EMAIL не має ліміту часу й може перевищити 30 секунд на домені, де перевіряється кілька адрес-кандидатів; тоді клітинка показує помилку, а кредит уже витрачено.
  • Кожен, хто може редагувати таблицю, може відкрити її проєкт Apps Script, а з ним і ключ. Надавайте доступ на редагування лише тим, кому можна користуватися ключем, і перевипустіть ключ у Cold Leads (Налаштування → API-ключі), якщо це зміниться.

Як читати результати

ФункціяРезультатЗначення
VERIFY_EMAILvalid, 97 або 80, okПоштовий сервер прийняв скриньку (80 для рольових адрес на кшталт info@).
VERIFY_EMAILvalid, 75 або 65, smtp_unreachableДомен приймає пошту, але саму скриньку не перевірено.
VERIFY_EMAILrisky, catch_all або smtp_unknownСервер приймає будь-яку адресу або не дав однозначної відповіді.
VERIFY_EMAILrisky, timeoutПеревірці забракло часу; наступного разу вона виконається знову.
VERIFY_EMAILinvalid, mailbox_missing, no_mx або syntaxНе використовуйте цю адресу.
FIND_EMAILадреса, verifiedПоштовий сервер підтвердив скриньку.
FIND_EMAILадреса, patternПрипущення за поширеними форматами адрес; підтвердьте його перед використанням.
FIND_EMAILпорожньо, noneКандидата немає: домен не має поштового сервера або ім’я не вдалося використати.

Ліміти та вартість

  • 1 кредит Cold Leads за кожен виклик, на який не відповів кеш скрипту. Cold Leads бере оплату й за результати зі свого 30-денного кешу, тож кредити заощаджує саме кеш скрипту.
  • Apps Script дозволяє 20 000 викликів URL Fetch на день для особистих акаунтів і 100 000 для Google Workspace, а також 30 секунд на один виклик користувацької функції. Заповнення формулою тисяч рядків одночасно може завершитися помилкою Script invoked too many times per second; заповнюйте меншими блоками або використовуйте масове завдання для великих списків.
  • Cold Leads дозволяє 120 запитів на хвилину на ключ. API входить у тариф Business: $99 на місяць із 10 000 кредитів на місяць; додаткові пакети по 1 000 кредитів коштують $5.

Питання

Клітинка показує #ERROR!. Що сталося?

Наведіть курсор на клітинку, щоб прочитати повідомлення: відсутня властивість COLDLEADS_API_KEY, помилка Cold Leads на кшталт no_credits чи rate_limited або ліміт у 30 секунд для користувацьких функцій.

Чи витрачаються кредити, коли таблицю відкривають знову?

Sheets повторно виконує користувацьку функцію переважно тоді, коли змінюються її формула або вхідні клітинки. Якщо ж вона все-таки виконається знову з тими самими вхідними даними протягом шести годин, відповість кеш скрипту без звернення до Cold Leads.

Як перевірити тисячі адрес?

Використовуйте масове завдання замість користувацької функції: до 5 000 адрес за запит, з опитуванням до завершення. Сторінки про n8n і Python на цьому сайті показують повні версії.