Atlorium

Треть вашей клиентской базы — мусор. Разбираем, откуда он берётся и как перекрыть кран

Данные не портятся сами — их портит форма регистрации, в которой можно ввести что угодно. Показываем, где именно течёт, и как заткнуть течь одним запросом на этапе ввода.

11 минут чтения продакт-менеджер · руководитель CRM · маркетинг · разработчик

В понедельник маркетинг запустил SMS-акцию по всей базе: 42 тысячи контактов, накопленных за четыре года. Через час агрегатор вернул отчёт. Шесть тысяч номеров он отверг сразу — «неверный формат». Ещё около двух тысяч принял, списал деньги за отправку, но статус доставки по ним так и не изменился.

В тот же день логистика разбиралась с курьером, который два часа искал «мск тверская 7 к2» — в накладной корпус слился с домом. А продажи в третий раз за неделю позвонили клиенту, который уже купил: его номер лежал в базе четырежды, потому что записан был по-разному — с восьмёркой, с плюсом, с пробелами и один раз с припиской «звонить после 18».

Три отдела, три разные аварии, один источник. Никто не портил базу нарочно. Её испортила форма, в которой телефон, почта и адрес были обычными текстовыми полями: введи что угодно, сохранится что угодно. Данные не гниют со временем сами по себе — они приходят битыми и остаются такими, пока кто-нибудь не наткнётся.

Где именно течёт

Мусор попадает в базу четырьмя путями, и они очень разные по природе.

Свободный ввод. Пользователь заполняет форму на телефоне, одной рукой, в метро. Он опечатается в домене, забудет цифру в номере, впишет адрес так, как привык говорить. Поле это примет, потому что поле — это просто <input type="text">.

Импорты. Файл с выставки, выгрузка от партнёра, старая база из системы, которую закрыли пять лет назад. Тут проверок нет вообще: данные заливают напрямую в таблицу, и мусор в них уже был на момент выгрузки.

Намеренная ерунда. За скидку человек оставит одноразовый ящик и номер, набранный случайными цифрами. Это не злоумышленник — это обычный клиент, который не хочет спама.

Время. Люди меняют работу, а с ней и корпоративную почту; съезжают с квартиры; бросают номер. Этот источник единственный, где регулярная перепроверка действительно нужна — остальные три лечатся на входе.

Курьер уехал не туда

Что происходит
Адрес хранится одной строкой: «мск, тверская 7 к2 кв 5». Города нет в отдельном поле, корпус слип с номером дома, и в накладную всё ушло ровно в таком виде.
Во что обходится
Повторный выезд, звонок оператора и отменённый заказ, если доставка была день-в-день. Курьер при этом потратил два часа, за которые мог бы развезти ещё четыре точки.
Чем закрывается
Стандартизация адреса разбирает строку на уровни (город, улица, дом, корпус, квартира) и возвращает коэффициент качества 0–100. Всё, что ниже вашего порога, уходит оператору на проверку, а не в накладную.

Письмо с чеком отскочило

Что происходит
Клиент ввёл адрес на домене gmial.com. В базе адрес есть, письмо ушло, ящика не существует — сервер вернул отказ.
Во что обходится
Клиент написал в поддержку, оператор потратил восемь минут, чтобы выяснить, что в домене пропущена буква. Отскок при этом ещё и портит репутацию домена-отправителя.
Чем закрывается
Проверка почты видит, что домен похож на популярный с опечаткой, и возвращает в поле didYouMean готовое исправление. Форма показывает подсказку до сохранения — клиент нажимает «да, я это имел в виду».

Один клиент считается четырьмя

Что происходит
В колонке с телефонами лежат «8 916 …», «+7 916 …», «916 …» и «+7(916) …». Для дедупликации это четыре разных значения.
Во что обходится
Менеджер звонит одному человеку несколько раз, клиент раздражается, а отчёт по конверсии показывает четыре лида вместо одного — и на этих цифрах кто-то принимает решения.
Чем закрывается
Валидация телефона приводит номер к единой канонической записи E.164 (+79161234567). По ней дубли схлопываются одним GROUP BY, а строки, которые телефоном не являются, отсекаются на входе.

Сколько это стоит

Про «треть базы» стоит договориться сразу: это не универсальная константа, а частый итог первой честной ревизии. У кого-то будет 12 %, у кого-то — половина. Единственная цифра, которой можно верить, — своя.

Замерить её несложно. Возьмите случайную выборку из базы — тысячи контактов достаточно — и прогоните через три проверки. Посчитайте долю записей, где почта недоставляема, номер невалиден или адрес не разбирается до дома. Умножьте на размер базы. Это и есть ваш процент мусора, и он получен на ваших данных, а не в чужой презентации.

Отдельная статья расходов, которую обычно не считают, — время людей. Восемь минут оператора на разбор одного «мне не пришёл чек» выглядят пустяком ровно до момента, когда таких обращений двести в месяц: это 27 часов, почти четыре рабочих дня, потраченных на последствия опечатки в форме.

Что происходит Без проверки на входе С проверкой на входе
Опечатка в домене почты Письма молча отскакивают, клиент считает вас ненадёжными Форма показывает подсказку с исправлением, пока человек ещё на странице
Номер записан как попало Дубли в CRM, повторные звонки, SMS уходят в никуда Единый формат E.164 в базе, дубли схлопываются автоматически
Адрес одной строкой Курьер разбирается на месте, оператор звонит уточнять Адрес разложен по полям, качество разбора известно числом
Одноразовый ящик в лидах Отдел продаж обрабатывает контакт, до которого не дозвониться Лид помечен ещё до попадания в воронку

Как чинить на входе

Порядок проверок диктует их себестоимость. Сначала то, что считается локально и мгновенно, потом то, что ходит в сеть.

Телефон (сервис «Валидация телефона»). Разбор офлайн, по справочнику мировой нумерации: ответ приходит за единицы миллисекунд. Смотреть надо на isValid — это значит, что номер записан правильно и попадает в диапазон, реально выделенный в его стране. Поле e164 кладите в базу вместо того, что ввёл пользователь, а по numberType отсекайте стационарные номера, если собираетесь слать SMS. Строку, которая телефоном не является, сервис отклоняет кодом 400 и не тарифицирует: работы по ней не было.

Адрес (сервис «Стандартизация адреса»). Тоже локальный разбор. На вход — строка, какой её ввёл человек, с сокращениями и опечатками. На выходе — массив components с уровнями от города до квартиры, собранная строка normalized и коэффициент quality от 0 до 100. Порог выбираете вы: например, всё ниже 80 отправлять оператору на глаза, остальное принимать молча.

Адрес в реестре (сервис «Адреса ГАР/ФИАС»). Это другая задача, и её часто путают с предыдущей. Стандартизация отвечает на вопрос «как эта строка устроена». ГАР отвечает на вопрос «существует ли такой адрес на самом деле» — по официальной выгрузке ФНС, с ОКТМО, ОКАТО, почтовым индексом и стабильным GUID объекта. В форме удобнее /api/gar/suggest: человек печатает, ему падают подсказки, он выбирает готовый объект реестра — и тогда мусора в поле не появится вовсе.

Почта (сервис «Проверка почты»). Единственная из четырёх, которая ходит наружу: проверка ведёт служебный диалог с почтовым сервером домена и выясняет, существует ли ящик. Письмо при этом не отправляется, владелец адреса ничего не получает и о проверке не узнаёт.

Код: проверки в форме и чистка базы

Все примеры ниже работают прямо сейчас, без регистрации: в них подставлен публичный демо-ключ ak_sandbox_demo_mockdata_v1. Он отдаёт моки — правдоподобные сгенерированные данные, а не настоящий результат проверки. Зато они детерминированы: один и тот же запрос всегда даёт один и тот же ответ, так что на песочнице можно писать тесты. Реальные вердикты приходят с личным ключом. Sandbox-ответ помечен заголовком X-Atlorium-Sandbox: true.

Проверяем почту перед сохранением лида

curl -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     "https://atlorium.com/api/emailvalidation?email=ivan@example.com"
        

import requests

BASE = "https://atlorium.com"
HEADERS = {"Authorization": "Bearer ak_sandbox_demo_mockdata_v1"}

def check_email(address: str) -> dict:
    resp = requests.get(
        f"{BASE}/api/emailvalidation",
        params={"email": address},
        headers=HEADERS,
        timeout=15,
    )
    # 400 — адрес синтаксически негодный, деньги не списаны.
    # 503 — источник недоступен, деньги тоже не списаны: можно спокойно повторить.
    resp.raise_for_status()
    return resp.json()

report = check_email("ivan@example.com")

# Итоговый вердикт: Valid | Invalid | CatchAll | Unknown | SpamTrap | Abuse | DoNotMail
status = report["status"]

if status in ("Invalid", "SpamTrap", "Abuse"):
    raise ValueError("Такой адрес в базу не берём")

if report.get("didYouMean"):
    # Опечатка в домене: покажите подсказку человеку, пока он ещё в форме.
    print("Возможно, вы имели в виду:", report["didYouMean"])
        

using System.Net.Http.Headers;
using System.Net.Http.Json;

var http = new HttpClient { BaseAddress = new Uri("https://atlorium.com") };
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", "ak_sandbox_demo_mockdata_v1");

var email = Uri.EscapeDataString("ivan@example.com");
var report = await http.GetFromJsonAsync<JsonElement>(
    $"/api/emailvalidation?email={email}");

// deliverable = true только у статуса Valid: catch-all и unknown — это неопределённость,
// а не разрешение писать.
var deliverable = report.GetProperty("deliverable").GetBoolean();
        

{
  "email": "ivan@example.com",
  "status": "Valid",
  "statusRaw": "valid",
  "subStatus": null,
  "account": "ivan",
  "domain": "example.com",
  "freeEmail": false,
  "catchAllDomain": false,
  "didYouMean": null,
  "domainAgeDays": 5842,
  "smtpProvider": "zoho",
  "mxFound": true,
  "mxRecord": "mx1.example.com",
  "activeInDays": null,
  "activeFirstSeen": null,
  "processedAtUtc": "2026-07-14T09:12:41Z",
  "elapsedMs": 486,
  "deliverable": true
}
        

Тот же вызов на шести языках (Python, TypeScript, Go, Java, C#, PHP) — в репозитории email-verification-api-client. Примеры запускаются сразу: ключ в них уже стоит демонстрационный.

Семь статусов — это семь разных решений, а не «хорошо / плохо». Вот таблица, которую можно прямо переносить в код.

status Что случилось Что делать в форме
Valid Ящик существует и принимает почту Сохранять
Invalid Ящика нет либо домен почту не принимает Не сохранять, показать ошибку. Если пришло didYouMean — предложить исправление
CatchAll Домен отвечает «валидно» на любой адрес — проверить конкретный ящик снаружи невозможно Сохранять, но пометить. Часть писем на такой домен может отскочить
Unknown Сервер домена не ответил, применил greylisting или заблокировал проверку Не блокировать человека. Повторить позже. Этот исход не тарифицируется
SpamTrap Адрес-ловушка для спамеров Не сохранять и никогда не отправлять — письмо ударит по репутации домена
Abuse Владелец адреса ранее жаловался на спам В рассылку не брать
DoNotMail Одноразовый ящик, ролевой адрес (info@, sales@) или токсичный домен Смотреть на subStatus: для B2B-лида ролевой адрес нормален, для рассылки — нет

В песочнице поведение можно выбирать доменом: invalid.example.com вернёт Invalid, catchall.example.comCatchAll, disposable.example.comDoNotMail с уточнением disposable. Так все ветки обработки тестируются без единого настоящего запроса.

Телефон и адрес: разбор на нашей стороне, ответ за миллисекунды

# Номер: знак «+» в URL кодируется как %2B
curl -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     "https://atlorium.com/api/phone/%2B79161234567"

# Номер без «плюса» — тогда обязателен countryCode
curl -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     "https://atlorium.com/api/phone/89161234567?countryCode=RU"

# Адрес одной строкой; region подсказывает разбору верхний уровень
curl -G -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     --data-urlencode "address=мск тверская 7к2 кв5" \
     --data-urlencode "region=77" \
     "https://atlorium.com/api/addressstd"
        

import requests

BASE = "https://atlorium.com"
HEADERS = {"Authorization": "Bearer ak_sandbox_demo_mockdata_v1"}

def normalize_phone(raw: str, country: str = "RU") -> str | None:
    """Возвращает номер в E.164 — каноническую форму для хранения и дедупликации."""
    resp = requests.get(
        f"{BASE}/api/phone/{raw}",
        params={"countryCode": country},
        headers=HEADERS,
        timeout=10,
    )
    if resp.status_code == 400:
        return None  # это вообще не телефон, запрос не тарифицирован
    number = resp.json()["number"]
    return number["e164"] if number["isValid"] else None

def parse_address(line: str, region: int | None = None) -> dict:
    resp = requests.get(
        f"{BASE}/api/addressstd",
        params={"address": line, "region": region},
        headers=HEADERS,
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()

addr = parse_address("мск тверская 7к2 кв5", region=77)

# quality — уверенность разбора, 0..100. Порог выбираете вы.
if addr["quality"] < 80:
    print("Отправить оператору на проверку:", addr["input"])
else:
    levels = {c["level"]: c["text"] for c in addr["components"]}
    print(levels.get("Street"), levels.get("Building"))
        

// GET /api/phone/%2B79161234567
{
  "input": "+79161234567",
  "number": {
    "isValid": true,
    "isPossible": true,
    "e164": "+79161234567",
    "international": "+7 916 123-45-67",
    "national": "8 (916) 123-45-67",
    "rfc3966": "tel:+7-916-123-45-67",
    "countryCallingCode": 7,
    "countryCode": "RU",
    "country": "Россия",
    "numberType": "Mobile",
    "originalCarrier": "МТС",
    "location": null,
    "timeZones": ["Europe/Moscow"]
  },
  "elapsedMs": 2
}

// GET /api/addressstd?address=мск тверская 7к2 кв5®ion=77
{
  "input": "мск тверская 7к2 кв5",
  "normalized": "город Москва, улица Тверская, д.7 корп.2, кв.5",
  "quality": 100,
  "countryCode": "RU",
  "message": null,
  "info": null,
  "components": [
    { "level": "RegionCity", "levelTitle": "Субъект РФ", "text": "город Москва",
      "types": ["город"], "names": ["Москва"] },
    { "level": "Street", "levelTitle": "Улица", "text": "улица Тверская",
      "types": ["улица"], "names": ["Тверская"] },
    { "level": "Building", "levelTitle": "Дом", "text": "д.7 корп.2",
      "number": "7", "buildNumber": "2" },
    { "level": "Apartment", "levelTitle": "Квартира", "text": "кв.5", "number": "5" }
  ],
  "extra": null,
  "milliseconds": 7,
  "gar": null,
  "geo": null
}
        

Тот же вызов на шести языках (Python, TypeScript, Go, Java, C#, PHP) — в репозитории phone-validation-api-client. Примеры запускаются сразу: ключ в них уже стоит демонстрационный.

Последний кусок — подсказки адреса из официального реестра. Это тот случай, когда проверка не нужна вовсе: человек выбирает готовый объект, и в базу попадает не строка, а GUID, индекс и ОКТМО.

Подсказки адреса из реестра ГАР/ФИАС

# Автодополнение для поля ввода (минимум 2 символа)
curl -G -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     --data-urlencode "query=Москва Тверская" \
     --data-urlencode "limit=7" \
     "https://atlorium.com/api/gar/suggest"

# Полный поиск: вернёт индекс, ОКТМО, ОКАТО и GUID объекта
curl -G -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     --data-urlencode "query=Москва ул Тверская д 7" \
     "https://atlorium.com/api/gar/search"
        

// GET /api/gar/search?query=Москва ул Тверская д 7
{
  "query": "Москва ул Тверская д 7",
  "results": [
    {
      "fullAddress": "г Москва, ул Тверская, д. 7",
      "objectType": "дом",
      "objectGuid": "0c5b2444-70a0-4932-980c-b4dc0d3f02b5",
      "objectId": 1405113,
      "regionCode": "77",
      "postalCode": "125009",
      "oktmo": "45382000",
      "okato": "45286585000"
    }
  ],
  "count": 1,
  "elapsedMs": 18
}
        

Тот же вызов на шести языках (Python, TypeScript, Go, Java, C#, PHP) — в репозитории gar-fias-address-api-client. Примеры запускаются сразу: ключ в них уже стоит демонстрационный.

Чего это не умеет

Раздел, ради которого стоило писать всё остальное. Если вы ждёте от этих сервисов того, чего в них нет, чистая база не появится — появится ложное чувство, что она чистая.

Проверка почты не отправляет письмо. Она выясняет, существует ли ящик и примет ли сервер письмо, — но подтвердить, что человек этот ящик читает, может только письмо с кодом. Это разные проверки, и они дополняют друг друга, а не заменяют. Отдельная честность про catch-all: на домене, который отвечает «валидно» на любой адрес, существование конкретного ящика снаружи не проверит никто. Мы возвращаем CatchAll, а не выдаём догадку за вердикт.

Стандартизация адреса не подтверждает, что адрес существует. Базовый разбор работает со строкой: выделяет уровни и нормализует запись. Улица, которой нет в реальности, разберётся прекрасно. Существование подтверждает сверка с реестром — это ГАР, отдельный сервис. Комбо-режимы bind (привязка разбора к ГАР) и geocode (координаты) в контракте уже описаны, но пока не реализованы: запрос с ними возвращает 501 и не тарифицируется.

ГАР отдаёт ровно то, что отдала официальная выгрузка ФНС. Свежая новостройка может в реестре ещё не значиться, а какой-то объект — прийти без индекса. Мы не дописываем данные за государство и не обещаем полноты, которой у источника нет.

Мы не дедуплицируем базу за вас. Сервисы дают канонические ключи — e164 для номера, нормализованный адрес, GUID объекта реестра. Схлопывание дублей по этим ключам — ваша логика, и она зависит от того, что в вашем бизнесе считается одним клиентом.

И про деньги, раз уж речь о честности. Если внешний источник упал, проверка почты вернёт 503, и списания не будет. Не тарифицируются и синтаксически негодный адрес, и строка, которая не является телефоном, и статус Unknown, когда сервер домена не дал ответа. Платите за вердикт, а не за попытку.

Частые вопросы

Частые вопросы

Чем проверка почты отличается от письма с кодом подтверждения?

Письмо с кодом проверяет, что человек читает ящик, и требует от него действия. Проверка доставляемости отвечает раньше и молча: существует ли ящик, примет ли сервер домена письмо, не одноразовый ли это адрес и не спам-ловушка. Письмо при этом не отправляется, владелец адреса о проверке не узнаёт. Проверки дополняют друг друга: первая отсеивает мусор до регистрации, вторая подтверждает владение.

Можно ли узнать, активен ли номер телефона?

Через наш сервис — нет, и это осознанное ограничение. Ответ на вопрос «работает ли номер и кто им пользуется» есть только у сети оператора и относится к конкретному человеку. Валидация телефона отвечает на другой вопрос: правильно ли номер записан и попадает ли он в диапазон, реально выделенный в его стране. Этого достаточно, чтобы отсечь опечатки и привести базу к единому формату.

Чем стандартизация адреса отличается от поиска в ГАР/ФИАС?

Стандартизация разбирает строку, которую ввёл человек: выделяет город, улицу, дом, квартиру и оценивает качество разбора. Она не сверяется со справочником, поэтому несуществующая улица разберётся так же хорошо, как настоящая. ГАР ищет адрес в официальном реестре ФНС и возвращает GUID объекта, почтовый индекс, ОКТМО и ОКАТО. Первое приводит данные в порядок, второе подтверждает, что адрес существует.

Сколько стоит проверить базу из 50 000 контактов?

Оплата идёт по факту, за каждый запрос, без абонентской платы и минимального платежа. Разбор телефона и адреса выполняется локально и стоит дешевле проверки почты, которая обращается к почтовому серверу домена. Актуальные цены — на странице тарифов; арифметика простая: цена запроса, умноженная на размер базы, против цены ошибок, которые вы уже оплачиваете.

Что будет, если сервис не смог проверить адрес?

Вы не заплатите. Статус unknown (почтовый сервер применил greylisting, не ответил или заблокировал проверку) не тарифицируется, синтаксически негодный адрес отклоняется до списания, а при недоступности внешнего источника возвращается код 503 и деньги тоже не списываются. Списание фиксируется только за содержательный вердикт.

Можно ли попробовать API без регистрации?

Да. В примерах кода стоит публичный демо-ключ ak_sandbox_demo_mockdata_v1: скопируйте curl и получите ответ прямо сейчас. Демо-ключ отдаёт моки — сгенерированные данные того же формата, что и настоящие, но детерминированные, так что на них можно писать тесты. Реальные вердикты приходят с личным ключом после регистрации.

Сервисы из этой статьи

Проверка почты

Проверка доставляемости адреса: существует ли ящик, MX, catch-all, одноразовый ящик, спам-ловушка, подсказка по опечатке

Валидация телефона

Разбор номера офлайн: валидность, формат E.164, страна, тип номера, оператор диапазона, регион, часовые пояса. Активность номера в сети не проверяется

Стандартизация адреса

Разбор адресной строки в структуру: уровни (регион/город/улица/дом/квартира), нормализация, коэффициент качества

Адреса ГАР/ФИАС

Поиск и нормализация адресов по справочнику ГАР/ФИАС

Попробовать прямо сейчас — без регистрации

Демо-ключ ak_sandbox_demo_mockdata_v1 — публичный и общий для всех. С ним API отвечает моками: данные правдоподобные, но сгенерированные, и они не меняются от запроса к запросу — на них удобно писать тесты. Настоящие данные приходят с личным ключом.

curl -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     https://atlorium.com/openapi/email_ru.json
Полезно? Перешлите коллеге: Telegram VK

Восстанавливаем соединение…

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

Не удалось переподключиться

Проверьте интернет-соединение. Можно повторить попытку или обновить страницу.

Сессия устарела

Соединение восстановлено, но сессию нужно перезагрузить. Обновите страницу, чтобы продолжить.