Cold Leads

Як працює виявлення accept-all і як читати результати Cold Leads

Для кого
Розробники й ops-команди, які вирішують, що робити з результатами risky
Проблема
Деякі поштові сервери приймають пошту для будь-якої адреси на своєму домені, тож перевірка скриньки не відрізнить справжню людину від вигаданого імені. Інші сервери дають лише тимчасові відповіді або взагалі недосяжні, а сама мітка valid може приховати те, що скриньку так і не перевірили.
Рішення
Розберіться, який SMTP-діалог веде сервіс перевірки і що означає кожна відповідь, а потім читайте статус, оцінку й коди причин, які повертає Cold Leads, зокрема у випадках, коли перевірка скриньки не виконувалася.
Що ви отримаєте
Таблиця рішень, що зіставляє кожен код причини Cold Leads із дією, і скрипт, який застосовує її до однієї адреси.

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

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

SMTP-діалог, що стоїть за перевіркою скриньки

Сервіс перевірки знаходить MX-хости домену й відкриває SMTP-з’єднання з одним із них на порту 25. Він вітається із сервером, називає відправника й одержувача, читає відповідь щодо одержувача і прощається, перш ніж було б надіслано будь-який лист. Друга сесія запитує той самий сервер про адресу, яка не може існувати.

Дві SMTP-сесії (ілюстрація)
# Session 1: is the address accepted?
S: 220 mx1.example.com ESMTP
C: EHLO verifier.example.net
S: 250-mx1.example.com
S: 250 SIZE 52428800
C: MAIL FROM:<check@verifier.example.net>
S: 250 2.1.0 Sender OK
C: RCPT TO:<anna@example.com>
S: 250 2.1.5 Recipient OK          <- accepted (a 550 here would mean: rejected)
C: QUIT                            <- no DATA command: nothing is delivered
S: 221 2.0.0 Bye

# Session 2: would the same server also accept an address that cannot exist?
S: 220 mx1.example.com ESMTP
C: EHLO verifier.example.net
S: 250 mx1.example.com
C: MAIL FROM:<check@verifier.example.net>
S: 250 2.1.0 Sender OK
C: RCPT TO:<zq7c41e09b2a6f5d@example.com>
S: 250 2.1.5 Recipient OK          <- also accepted: the server accepts all addresses
C: QUIT
S: 221 2.0.0 Bye

Коди відповідей і висновки, які з них можна зробити

Коди відповідей визначено в RFC 5321. Зміст несе перша цифра: 2 — успіх, 4 — тимчасова помилка, 5 — постійна помилка.

ВідповідьЗначення в RFC 5321Який висновок може зробити сервіс перевірки
220Сервіс готовий (вітальний банер)Сервер розмовляє SMTP; перевірку можна продовжувати.
250, 251, 252Дію виконано, лист буде переслано або перевірити неможливо, але лист буде прийнятоСервер зараз прийме пошту для цього одержувача. На accept-all сервері це нічого не каже про скриньку.
421Сервіс недоступний, канал закриваєтьсяВисновку немає; сервер зайнятий або відмовляє сервісу перевірки.
450, 451, 452Скринька недоступна, локальна помилка, недостатньо місця (тимчасово)Висновку немає. Сервери з грейлістингом так відповідають невідомим відправникам і чекають повторної спроби через кілька хвилин.
550Скринька недоступна, наприклад не знайдена, або відхилено політикоюЗазвичай скриньки не існує, але блокування сервера перевірки за політикою виглядає так само.
551, 552, 553, 554Користувач не локальний, перевищено обсяг сховища, недопустиме ім’я скриньки, транзакція не вдаласяОстаточна відмова цьому одержувачеві в цій сесії.

Перевірка accept-all, грейлістинг та інші обмеження

  • Accept-all: після того як справжню адресу прийнято, сервіс перевірки запитує той самий сервер про випадкову локальну частину на тому самому домені. Якщо її теж прийнято, перше прийняття нічого не означає, і результат — risky з причиною catch_all.
  • Грейлістинг (RFC 6647): сервер тимчасово відхиляє невідомі комбінації відправника й одержувача відповіддю 4xx і приймає повторну спробу пізніше. Один прохід бачить лише 4xx, а Cold Leads у межах однієї перевірки не чекає й не повторює спробу, тож результат — risky зі smtp_unknown.
  • Пізні відмови: деякі сервери під час діалогу приймають кожного одержувача, а невідомих відхиляють лише після того, як прийняли лист, — відмовою (bounce). Перевірка цього не бачить.
  • Блокування за політикою: сервер може відмовити самому хосту перевірки. Відмови на етапі привітання, EHLO або MAIL FROM Cold Leads трактує як smtp_unknown, а будь-яку відповідь 500–559 на RCPT TO — як mailbox_missing.
  • Порт 25: для перевірки потрібне вихідне SMTP-з’єднання. Багато хмарних і serverless-платформ блокують вихідний порт 25, і тоді діалогу взагалі не відбувається.

Що повертає Cold Leads і коли перевірка не виконувалася

Перевірка виконується в такому порядку: синтаксис (помилка завершує перевірку), вбудований список із 40 одноразових поштових доменів, список із 25 рольових імен (info, sales, support, admin та інші) і пошук MX, який за відсутності MX-записів переходить до A-запису домену. SMTP-перевірка скриньки та accept-all виконується лише тоді, коли Cold Leads може відкрити SMTP-з’єднання з одним із перших двох MX-хостів. Масив reasons завжди показує, який у вас випадок.

POST /api/v1/verify: домен, до поштового сервера якого не вдалося достукатися через SMTP
{
  "email": "anna@example.com",
  "status": "valid",
  "score": 75,
  "reasons": ["smtp_unreachable"],
  "mx": "mx1.example.com",
  "catch_all": false,
  "disposable": false,
  "role": false,
  "checked_at": "2026-09-30T08:15:02.114Z",
  "cached": false
}
Остання причинаstatusscoreЗначення
okvalid97 (80 для рольових адрес)Сервер прийняв адресу й не прийняв випадкову адресу.
catch_allrisky60Сервер прийняв і адресу, і випадкову адресу.
mailbox_missinginvalid2Сервер відхилив одержувача відповіддю від 500 до 559.
smtp_unknownrisky50Немає однозначної відповіді: тимчасова відповідь 4xx, відмова сервісу перевірки, MX-хост у приватній мережі або поєднання недосяжних і неоднозначних хостів.
smtp_unreachablevalid75 (65 для рольових адрес)Жоден MX-хост не відповів на порту 25 на з’єднання з хоста перевірки. Домен приймає пошту, але скриньку й accept-all не перевірено.
timeoutrisky50 або 60, якщо адресу прийнято, але перевірка accept-all не завершиласяВичерпано необов’язковий ліміт часу timeout_ms. Результат не кешується.
no_mxinvalid0Домен не має ні MX-, ні A-запису.
syntaxinvalid0Адреса не відповідає правилам синтаксису; більше нічого не перевіряється.
  • disposable і role, якщо вони стосуються адреси, стоять перед останньою причиною. Одноразовий домен робить статус risky з оцінкою 40 (ok), 30 (smtp_unreachable) або 20 (smtp_unknown).
  • catch_all дорівнює true лише тоді, коли перевірка accept-all виконалася і випадкову адресу прийнято. Значення false інформативне лише тоді, коли остання причина — ok або mailbox_missing; після smtp_unreachable, smtp_unknown чи timeout accept-all не перевірявся. MCP-інструмент verify_email, хостований чи локальний, у цих випадках повертає catch_all як null; REST API повертає false.
  • cached дорівнює true, коли результат узято з 30-денного кешу; тоді checked_at показує час початкової перевірки.
  • Результати масових завдань містять лише останній код причини — у полі reason.

Таблиця рішень і скрипт, який її застосовує

Остання причинаРекомендована дія
okНадсилати.
smtp_unreachableНадсилати обережно: невеликими порціями, одразу прибираючи жорсткі відмови. Скриньку не підтверджено.
catch_allПереглянути. Залишати лише тоді, коли є інша ознака того, що людина існує, наприклад відповідь або відомий шаблон адрес у цій компанії.
smtp_unknownПереглянути. Нова перевірка протягом 30 днів поверне результат із кешу й однаково коштуватиме кредит.
timeoutПеревірити ще раз із більшим timeout_ms, до 30 000; тайм-аути не кешуються.
mailbox_missing, no_mx, syntaxВідкинути.
є disposableВідкинути для B2B-розсилок.
decide.mjs
// node decide.mjs anna@example.com   (Node 18+, COLDLEADS_API_KEY=sk_... in the environment)
const email = process.argv[2];
const res = await fetch("https://coldleads.app/api/v1/verify", {
  method: "POST",
  headers: { "x-api-key": process.env.COLDLEADS_API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ email, timeout_ms: 10000 }),
});
const r = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${r.error}`);

const final = r.reasons[r.reasons.length - 1];
// catch_all only carries information when a mail server answered the probe
const smtpAnswered = ["ok", "catch_all", "mailbox_missing"].includes(final);
const ACTION = {
  ok: "send",
  smtp_unreachable: "send carefully: domain accepts mail, mailbox not checked",
  catch_all: "review: the server accepts every address",
  smtp_unknown: "review: no definite answer from the server",
  timeout: "check again with a larger timeout_ms",
  mailbox_missing: "drop",
  no_mx: "drop",
  syntax: "drop",
};
console.log({
  email: r.email,
  status: r.status,
  score: r.score,
  reasons: r.reasons,
  mailbox_checked: smtpAnswered,
  catch_all: smtpAnswered ? r.catch_all : "not tested",
  cached: r.cached,
  action: r.disposable ? "drop: disposable domain" : ACTION[final] ?? "review",
});

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

  • 1 кредит за перевірку, зокрема за помилки синтаксису й результати з 30-денного кешу.
  • timeout_ms необов’язковий і приймає від 1 000 до 30 000 мілісекунд; на будь-яке інше значення API відповідає 400 bad_timeout.
  • API входить у тариф Business: $99 на місяць із 10 000 кредитів на місяць; додаткові пакети по 1 000 кредитів коштують $5. 120 запитів на хвилину на ключ.

Питання

Чому адреса valid, якщо її скриньку не перевірено?

Бо те, що з хоста перевірки не вдалося достукатися до поштового сервера, не свідчить проти адреси. Статус відображає ті перевірки, які відбулися: синтаксис правильний, і домен приймає пошту. Нижча оцінка (75 замість 97) і причина smtp_unreachable показують, що саму скриньку не перевіряли.

Чи означає catch_all false, що домен не accept-all?

Лише якщо перевірка виконалася, — це видно з останньої причини ok або mailbox_missing. Після smtp_unreachable, smtp_unknown чи timeout accept-all взагалі не перевірявся.

Чи зміниться результат, якщо ще раз перевірити risky-адресу?

Протягом 30 днів нова перевірка повертає результат із кешу (cached: true) і однаково коштує 1 кредит. Результати з тайм-аутом не кешуються, тож їх варто перевірити ще раз із більшим timeout_ms.

Чи надсилає перевірка лист людині?

Ні. Діалог завершується командою QUIT одразу після кроку з одержувачем, до команди DATA, тож жоден лист не доставляється.