uCheckeruChecker
Марк АвриловОпубликовано обновлено 16 мин чтения

Email-валидация в формах регистрации: от regex до API

Большинство плохих адресов попадает в базу через формы: подписки, регистрации, оформления заказа. Каждый такой адрес потом стоит денег. ESP берёт плату за отправку, отказы копятся, репутация домена проседает. Отсеять мусор на входе проще, чем вычищать базу задним числом, и ниже по шагам разобрано, как это сделать.


Три слоя валидации: от быстрого к точному

Одним регулярным выражением валидация email в форме не обходится. Надёжная проверка состоит из трёх слоёв, и каждый ловит свой тип ошибок.

Слой 1: клиентская валидация (regex). Работает мгновенно, без сетевых запросов. Отсекает очевидные ошибки: пустое поле, отсутствие @, пробелы, двойные точки в домене. Не защищает от несуществующих ящиков или одноразовых сервисов, но даёт пользователю моментальную обратную связь.

Слой 2: серверная валидация (бизнес-логика). Проверки на стороне бэкенда: нормализация адреса, дедупликация, проверка по внутреннему чёрному списку доменов. Выполняется при отправке формы.

Слой 3: внешний API. Запрос к сервису валидации, который смотрит MX-записи домена, спрашивает почтовый сервер по SMTP, есть ли такой ящик, и сверяет домен с признаками одноразовых сервисов. Самый точный слой и самый медленный: у uChecker проверка одного адреса занимает от нескольких секунд до нескольких минут, дольше всего отвечают домены с жёсткой антиспам-политикой. Поэтому форма его ответа не ждёт, об этом подробно ниже.

Все три слоя работают вместе. Regex ловит опечатки до отправки запроса. Сервер фильтрует дубли и известные мусорные домены. API проверяет то, что прошло первые два фильтра.

Что такое проверка email в реальном времени

Так называют проверку адреса в тот момент, когда человек его вводит. Адрес уходит на проверку один, сразу после ввода, а не вместе со всей базой раз в квартал. По-английски это называют real-time email verification.

Главное место для неё, конечно, формы регистрации и подписки. Но есть ещё два. Оформление заказа: покупатель оставляет email для чека и статуса доставки, и с опечаткой в адресе подтверждение до него просто не дойдёт. CRM и внутренние формы, куда менеджер вбивает адрес клиента руками, и ошибку вы увидите на этапе ввода, а не после первого возврата письма.

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

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

И про персональные данные. Адрес из формы уходит стороннему сервису, это обработка персональных данных, так что проверку разумно упомянуть в политике конфиденциальности сайта. Если у вас есть посетители из ЕС, по GDPR сервис проверки выступает обработчиком данных (статья 28), и с ним нужен договор об обработке, DPA. Уточните у сервиса и то, сколько он хранит проверенные адреса и как их удалить.

Клиентская валидация: regex и UX

Регулярное выражение для email не обязано быть идеальным. RFC 5322 допускает адреса вроде "quoted string"@example.com, но в живых формах такие почти не встречаются. Простой regex, которого хватает на практике:

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;

function validateEmailFormat(email) {
  if (!email) return 'Введите email';
  if (!EMAIL_RE.test(email.trim())) return 'Некорректный формат email';
  return null; // ошибок нет
}

Этот regex не пытается учесть все крайние случаи из RFC. Он проверяет минимум: что-то до @, что-то после, точка и домен верхнего уровня длиной от двух символов. Для первой линии обороны этого достаточно.

Распространённая ошибка: показывать сообщение об ошибке, пока пользователь ещё печатает. Адрес user@ невалиден, но человек ещё не дописал. Красное сообщение в этот момент раздражает. Проверять формат лучше по событию blur (пользователь ушёл из поля) или с debounce в 300-500 мс.

const emailInput = document.getElementById('email');
const errorEl = document.getElementById('email-error');

emailInput.addEventListener('blur', () => {
  const error = validateEmailFormat(emailInput.value);
  errorEl.textContent = error || '';
  emailInput.classList.toggle('border-red-400', !!error);
});

Для React или Vue логика та же: валидация по blur, а не по каждому нажатию клавиши. Ошибка появляется, когда пользователь закончил ввод. Исчезает, когда он начинает исправлять.

Серверная валидация: нормализация и дедупликация

Клиентскую валидацию можно обойти, открыв DevTools и отправив POST-запрос напрямую. Поэтому серверная проверка обязательна, даже если на фронтенде всё идеально.

На сервере добавьте нормализацию. Люди пишут John.Doe@Gmail.Com и не думают о регистре. Домен его не различает. Имя ящика RFC 5321 формально считает чувствительным к регистру, но полагаться на это не советует, и почтовые сервисы так не делают. У личных ящиков gmail.com свои правила: точки в имени Google игнорирует, а всё после плюса считает меткой того же ящика. Без нормализации один человек регистрируется дважды.

// Ключ для поиска дублей. Отправлять письма лучше
// на адрес в том виде, в каком его ввели.
function dedupKey(email) {
  let [local, domain] = email.trim().toLowerCase().split('@');

  // Только gmail.com: у Google Workspace и других доменов
  // точки в имени ящика значимы
  if (domain === 'gmail.com') {
    local = local.split('+')[0].replace(/\./g, '');
  }

  return local + '@' + domain;
}

// dedupKey('John.Doe+spam@Gmail.Com')
// => 'johndoe@gmail.com'

Ещё на сервере удобно сверять домен с открытыми списками одноразовых сервисов. Списки обновляются, но новые одноразовые домены появляются постоянно, и какой-нибудь всегда окажется свежее последнего обновления, его поймает уже третий слой.

Проверка через API uChecker

Внешний API делает то, что из браузера сделать нельзя: смотрит MX-записи, разговаривает с почтовым сервером по SMTP, сверяет домен с признаками одноразовых сервисов и чёрными списками. Вызывать его нужно с бэкенда. API-ключ передаётся в заголовке x-api-key, и в клиентском коде его прочитает любой, кто откроет вкладку Network.

Как устроен метод POST /api/v1/validate/single. Сначала адрес проходит проверку синтаксиса. Если формат заведомо неверный, ответ приходит сразу, со "status": "invalid", и кредит не списывается. Если формат в порядке, адрес встаёт в очередь, списывается один кредит, и в ответе приходит task_id. Итог проверки забирают двумя способами: опрашивают задачу через GET /api/v1/tasks/{taskId} и потом читают /results, либо передают webhook_url, и uChecker сам пришлёт POST, когда закончит.

Для формы удобно сочетать оба способа. Бэкенд ждёт результат несколько секунд и опрашивает задачу раз в 5 секунд, как советует документация. Если результат успел прийти, форма сразу показывает ответ. Если нет, регистрация проходит, контакт помечается как непроверенный, а итог потом доставит webhook. Пример на Node.js (Express):

const UC = 'https://api.uchecker.net/api/v1';
const headers = {
  'Content-Type': 'application/json',
  'x-api-key': process.env.UCHECKER_API_KEY,
};
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// POST /api/validate-email
app.post('/api/validate-email', async (req, res) => {
  const email = String(req.body.email || '').trim();

  try {
    const task = await fetch(`${UC}/validate/single`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        email,
        webhook_url: 'https://example.com/hooks/uchecker',
      }),
      signal: AbortSignal.timeout(5000),
    }).then((r) => r.json());

    // Синтаксис не прошёл: ответ мгновенный, кредит не списан
    if (task.status === 'invalid') return res.json({ status: 'invalid' });
    // 401, 403 (кончились кредиты) и прочие ошибки: пропускаем
    if (!task.task_id) return res.json({ status: 'pending' });

    // Два опроса с паузой 5 с, дальше результат придёт на webhook
    for (let i = 0; i < 2; i++) {
      await sleep(5000);
      const state = await fetch(`${UC}/tasks/${task.task_id}`, { headers })
        .then((r) => r.json());
      if (state.status === 'failed') break;
      if (state.status !== 'completed') continue;

      const { data } = await fetch(
        `${UC}/tasks/${task.task_id}/results`, { headers }
      ).then((r) => r.json());
      // validation_result: good | bad | unknown
      return res.json({ status: data[0].validation_result });
    }
    return res.json({ status: 'pending' });
  } catch (err) {
    // API недоступен: пропускаем, проверим позже
    console.error('Validation API error:', err.message);
    return res.json({ status: 'pending' });
  }
});

Посмотрите на ветки с pending. Если API валидации временно недоступен или кредиты кончились, форма ломаться не должна. Лучше пропустить сомнительный адрес и проверить его позже, чем показать человеку ошибку сервера на шаге регистрации.

Webhook присылает сводку по задаче (task_id, status и счётчики good, bad, unknown), поэтому вердикт по самому адресу ваш обработчик забирает отдельным запросом к результатам задачи. Адрес webhook должен быть доступен из интернета и отвечать 200, на внутренние адреса uChecker не шлёт.

// POST /hooks/uchecker: uChecker вызывает его, когда задача закончена
app.post('/hooks/uchecker', async (req, res) => {
  res.sendStatus(200);
  const { task_id, status } = req.body;
  if (status !== 'completed') return;

  const { data } = await fetch(`${UC}/tasks/${task_id}/results`, { headers })
    .then((r) => r.json());
  for (const row of data) {
    // ваша функция: записать вердикт в карточку контакта
    await markContact(row.email, row.validation_result, row.result);
  }
});

Полный список методов, коды ошибок и песочница для запросов собраны в документации API.

Фронтенд: запрос к бэкенду и обработка ответа

На клиенте две задачи: отправить запрос в правильный момент и показать результат так, чтобы человек не раздражался. Хватает трёх вещей: проверки по blur, состояния загрузки и понятных сообщений.

const emailField = document.getElementById('email');
const statusEl = document.getElementById('email-status');
let controller = null; // AbortController для отмены предыдущего запроса

emailField.addEventListener('blur', () => {
  const email = emailField.value.trim();
  const formatError = validateEmailFormat(email);

  if (formatError) {
    showStatus(formatError, 'error');
    return;
  }

  // Показать индикатор проверки
  showStatus('Проверяем...', 'loading');

  // Отменяем предыдущий запрос, если есть
  if (controller) controller.abort();
  controller = new AbortController();

  fetch('/api/validate-email', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email }),
    signal: controller.signal,
  })
    .then(r => r.json())
    .then(data => {
      if (data.status === 'bad') {
        showStatus('На этот адрес письмо не дойдёт. Нет ли опечатки?', 'error');
      } else if (data.status === 'invalid') {
        showStatus('Некорректный формат email', 'error');
      } else {
        // good, unknown, pending: не мешаем регистрации
        showStatus('', 'ok');
      }
    })
    .catch(err => {
      if (err.name !== 'AbortError') {
        showStatus('', 'ok'); // не блокируем при ошибке сети
      }
    });
});

function showStatus(message, type) {
  statusEl.textContent = message;
  statusEl.className = {
    error: 'text-red-600 text-sm mt-1',
    loading: 'text-gray-400 text-sm mt-1',
    ok: '',
  }[type] || '';
}

Ключевой момент здесь AbortController. Если пользователь быстро переключается между полями или исправляет email, предыдущий запрос отменяется. Без этого ответы приходят в случайном порядке, и статус может показать результат для старого значения поля.

Пример для React

В React-приложениях логика та же, только обёрнута в хук. Вот поле email с валидацией:

import { useState, useRef, useCallback } from 'react';

function useEmailValidation() {
  const [status, setStatus] = useState('idle');
  // idle | checking | valid | invalid
  const [message, setMessage] = useState('');
  const controllerRef = useRef(null);

  const validate = useCallback(async (email) => {
    // Формат
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email)) {
      setStatus('invalid');
      setMessage('Некорректный формат email');
      return;
    }

    setStatus('checking');
    setMessage('');

    if (controllerRef.current) controllerRef.current.abort();
    controllerRef.current = new AbortController();

    try {
      const res = await fetch('/api/validate-email', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email }),
        signal: controllerRef.current.signal,
      });
      const data = await res.json();

      if (data.status === 'bad' || data.status === 'invalid') {
        setStatus('invalid');
        setMessage('На этот адрес письмо не дойдёт');
      } else {
        setStatus('valid'); // good, unknown, pending
        setMessage('');
      }
    } catch (err) {
      if (err.name !== 'AbortError') {
        setStatus('valid'); // сеть упала: не держим форму
        setMessage('');
      }
    }
  }, []);

  return { status, message, validate };
}

Использование в компоненте формы:

function SignupForm() {
  const { status, message, validate } = useEmailValidation();
  const [email, setEmail] = useState('');

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={e => setEmail(e.target.value)}
        onBlur={() => email && validate(email)}
        className={status === 'invalid' ? 'border-red-400' : ''}
      />
      {status === 'checking' && <span>Проверяем...</span>}
      {message && <span className="text-red-600">{message}</span>}

      <button type="submit" disabled={status === 'invalid'}>
        Зарегистрироваться
      </button>
    </form>
  );
}

Кнопка блокируется только при явной ошибке. Пока идёт проверка, её можно нажать: бэкенд всё равно примет адрес и доведёт проверку сам.

UX-паттерны: что работает, а что бесит

Техническая реализация без продуманного UX приносит больше вреда, чем пользы. Несколько правил, которые стоит держать в голове.

Не блокируйте ввод во время проверки. Человек заполнил email и перешёл к паролю, проверка идёт в фоне. Спиннер на всю форму заставляет его сидеть и ждать. Пусть заполняет дальше, результат появится, когда будет готов.

Конкретные сообщения об ошибках. "Некорректный email" не помогает. "Домен gmail.co не существует. Возможно, вы имели в виду gmail.com?" помогает. Чем точнее сообщение, тем больше шансов, что человек исправит ошибку, а не уйдёт.

Подсказки при типичных опечатках. Домены вроде gmial.com, yandex.tu, hotmal.com попадаются в любой форме с заметным трафиком. Хватает расстояния Левенштейна по короткому списку популярных доменов. Тут есть и второй резон: по FAQ Amazon SES, часть спам-ловушек живёт как раз на доменах с опечаткой в названии известного почтовика.

const COMMON_DOMAINS = [
  'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com',
  'mail.ru', 'yandex.ru', 'rambler.ru', 'icloud.com',
];

function suggestDomain(inputDomain) {
  const domain = inputDomain.toLowerCase();
  if (COMMON_DOMAINS.includes(domain)) return null;

  for (const known of COMMON_DOMAINS) {
    if (levenshtein(domain, known) <= 2) {
      return known; // "gmial.com" => подсказка "gmail.com"
    }
  }
  return null;
}

Визуальный статус, а не только текст. Зелёная галочка рядом с полем, когда email валиден, красная обводка при ошибке, серый спиннер во время проверки.

Не отклоняйте unknown. Этот статус значит, что выяснить ничего не удалось: MX-серверы домена не ответили, у домена нет MX-записей, он похож на одноразовый или числится в чёрных списках. Или домен принимает почту на любой адрес (catch-all), и его «да» про конкретный ящик ничего не доказывает. За таким бывают и живые корпоративные ящики. Пропустите такой адрес с пометкой в базе и решите его судьбу позже, например по тому, подтвердил ли человек подписку.

Обработка ошибок и edge cases

API может вернуть ошибку. Сеть может быть недоступна. Таймаут может сработать. Каждый из этих случаев нужно обработать, иначе форма сломается в продакшене.

Таймаут запроса. Бэкенд из примера выше сам ждёт ответа около десяти секунд, так что таймаут на клиенте ставьте с запасом. Не дождались, пропускайте человека, адрес проверится в фоне.

async function validateWithTimeout(email, timeoutMs = 15000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const res = await fetch('/api/validate-email', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email }),
      signal: controller.signal,
    });
    clearTimeout(timer);
    return await res.json();
  } catch (err) {
    clearTimeout(timer);
    // Таймаут или сетевая ошибка: пропускаем
    return { status: 'pending' };
  }
}

Кеширование. Каждый вызов validate/single с корректным адресом стоит кредит. Если человек дважды вводит один и тот же адрес, второй запрос к API не нужен.

const validationCache = new Map();

async function cachedValidate(email) {
  const normalized = email.trim().toLowerCase();

  if (validationCache.has(normalized)) {
    return validationCache.get(normalized);
  }

  const result = await validateWithTimeout(normalized);
  // pending не кешируем: результат ещё придёт
  if (result.status !== 'pending') validationCache.set(normalized, result);

  // Ограничим размер кеша
  if (validationCache.size > 500) {
    const firstKey = validationCache.keys().next().value;
    validationCache.delete(firstKey);
  }

  return result;
}

Повторные попытки. Ограничений на частоту запросов у API uChecker нет, так что 429 ждать не нужно. При 5xx и сетевой ошибке хватит одного повтора через секунду-другую, больше человек ждать не станет. Если пришёл 403, значит, кончились кредиты, и повторять нечего. Пропускайте адрес и пишите в лог.

Fallback при недоступности API. Если API валидации лежит, форма должна работать на первых двух слоях. Адрес попадёт в базу без полной проверки, и его можно будет прогнать позже пакетным методом. Потерянная регистрация обходится дороже одного сомнительного email.

Какие поля ответа API использовать

Для формы нужны четыре поля, и приходят они в разных ответах.

status в ответе validate/single. Значение invalid приходит сразу и означает ошибку синтаксиса, её можно показывать без ожидания. queued значит, что адрес ушёл на проверку.

task_id. Номер задачи. По нему опрашивают статус и забирают результат, его же присылает webhook.

validation_result (good / bad / unknown) в результатах задачи. Основной вердикт. Для bad показывайте ошибку: ящика нет, он отключён или домен не принимает почту. Для unknown пропускайте с пометкой. Пока адрес не проверен, в поле null.

result. Ответ проверяющего сервера SMTP-строкой, например 550 5.1.2 <risk> No MX records found. Это свободный текст, сравнивать его на точное равенство нельзя. Пользователю его показывать незачем, а в логе он объяснит, почему адрес отклонён.

Когда вызывать API: стратегии

Подходов три, и выбор зависит от конверсии формы и объёма трафика.

На blur. Проверка запускается, когда пользователь покидает поле email. Пока он заполняет остальное, ответ успевает прийти, и к нажатию кнопки "Зарегистрироваться" результат часто уже готов. Минус в расходе: каждое исправление адреса означает новый запрос и новый кредит.

На submit. Проверка только при отправке формы. API вызывается один раз, сколько бы человек ни правил адрес. Минус: ответ приходит, когда человек уже ждёт следующий экран. Подходит для форм с низким трафиком.

Отложенная. Форма принимает любой email, прошедший regex. Полная проверка запускается в фоне после регистрации, невалидные адреса помечаются, а человеку уходит просьба подтвердить адрес. Конверсию этот вариант бережёт сильнее всего, зато мусорные адреса попадают в базу.

Безопасность: чего не делать

Не вызывайте API валидации из браузера с ключом аккаунта. Ключ в заголовке x-api-key не истекает, и извлечь его из клиентского кода может любой. Все вызовы к внешним API идут через ваш бэкенд.

Поставьте ограничение частоты на свой эндпоинт /api/validate-email. Без него бот отправит тысячи запросов и за минуты выберет ваш баланс кредитов. Нескольких запросов в минуту с одного IP живому человеку хватает с головой.

Бот-регистрации: зачем боты заполняют вашу форму

Бот-регистрации (bot signups) это заявки в форму подписки или регистрации, которые отправляет программа. В каталоге автоматизированных угроз OWASP такая атака значится под номером OAT-019: массовое создание аккаунтов через штатную форму, чтобы потом ими злоупотреблять. С формой подписки то же самое, только вместо аккаунтов в базе копятся адреса, за которыми нет живого подписчика.

Подписочная бомбардировка. Бот вписывает чужой адрес в сотни форм подряд, и человека заваливает письмами с подтверждением. Spamhaus в 2016 году описывал волну таких атак: у одной компании девять адресов подписали больше 9000 раз за две недели, и вышло 81 000 писем с подтверждением. Рассылали их формы чужих сайтов, и ваша может оказаться среди них.

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

Спам в полях. В поле «Имя» приезжает ссылка или рекламная фраза. Расчёт на то, что приветственное письмо или публичный профиль покажет её кому-то ещё.

Заметить атаку обычно несложно. Подписок за день вдруг в разы больше обычного, заявки идут с одного IP или узкой подсети с интервалом в секунды, адреса похожи друг на друга (test1234@…, asdf8765@…). При double opt-in почти никто из новых не подтверждает подписку. Ещё один признак ловит нормализация из раздела выше: десяток «разных» адресов, которые после удаления точек сводятся к одному ящику gmail.com. Google прямо пишет, что помешать подписать человека на рассылку через вариант его адреса с точками не может.

Что помогает. Первое средство, подтверждение подписки. Google в требованиях к отправителям просит подтверждать адрес каждого получателя, прежде чем подписывать его. Бот введёт адрес, но по ссылке в чужом ящике не кликнет, и неподтверждённый адрес в рассылку не попадёт. От бомбардировки оно не спасает, ведь оружием там служат сами письма с подтверждением. Поэтому Spamhaus советует ставить на форму CAPTCHA вместе с подтверждением. Видимая CAPTCHA при этом отпугивает и часть живых подписчиков, так что решать вам: если атак на форму не было, можно начать с honeypot и лимитов.

Дальше по нарастающей. Honeypot, скрытое поле, которое человек не видит, а простой бот заполняет. Лимит заявок с одного IP на вашем сервере. Проверка адреса через API отсекает несуществующие ящики, но настоящий чужой адрес бот подставит так же легко, так что от ботов она защищает только вместе с остальным.

<!-- поле спрятано от людей, бот его заполнит -->
<input type="text" name="website" tabindex="-1" autocomplete="off"
       style="position:absolute;left:-9999px" aria-hidden="true">

// на сервере
if (req.body.website) return res.status(400).end(); // honeypot заполнен

Отравленные данные: что ещё попадает в базу

Отравленными данными (poisoned data) называют адреса, которые оказались в базе во вред отправителю: бот-регистрации, чужие адреса, вписанные без ведома владельца, спам-ловушки, одноразовые ящики. До первой рассылки их не видно.

Кроме ботов, источников три. Купленные базы: Google в тех же требованиях прямо просит не покупать адреса у других компаний, Amazon SES добавляет к этому аренду и обмен списками. Адреса, собранные с сайтов: по FAQ Amazon SES, ловушки часто нарочно размещают в интернете. И мусор, который люди вводят сами, чтобы скачать файл без подписки, вроде test@test.com. Такой адрес проходит регулярное выражение, и решает его судьбу уже SMTP-проверка.

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

Что из этого видит uChecker. Несуществующий или отключённый ящик и домен, который не принимает почту, получают bad. Домен без MX-записей, с молчащими MX-серверами, похожий на одноразовый сервис, из чёрного списка или catch-all получает unknown. А вот настоящий чужой адрес, вписанный ботом, проверку пройдёт, ящик-то существует. Его отсекает только подтверждение подписки.

Чеклист интеграции

Что вам нужно реализовать для надёжной валидации email в форме регистрации:

1. Regex-проверка формата на клиенте (мгновенная обратная связь).

2. Ключ дедупликации на сервере (регистр, точки и +метки gmail.com). Письма при этом отправляйте на адрес в том виде, в каком его ввели, ключ нужен только для поиска дублей.

3. Вызов API валидации с бэкенда (API-ключ не в браузере).

4. Короткое ожидание результата и webhook для остального.

5. Форма работает, даже если API недоступен.

6. Кеширование результатов (не тратить кредит повторно на тот же адрес).

7. Лимит запросов на своём эндпоинте валидации, иначе бот за минуты потратит ваши кредиты.

8. Конкретные сообщения об ошибках и подсказки при опечатках в домене.

9. Подтверждение подписки, CAPTCHA и honeypot против ботов. Проверка через API тут не поможет: чужой, но настоящий адрес она пропустит.

10. Отложенная пакетная проверка для адресов, принятых без полной валидации.

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

Источники

Попробуйте API валидации в деле: зарегистрируйтесь в uChecker и получите 100 бесплатных проверок для тестирования интеграции.

валидация email в формепроверка email при регистрацииemail validation APIreal-time проверка emailфронтенд валидацияJavaScript email validationReact email формабот-регистрацииотравленные данные

Марк Аврилов · Автор материала

Опубликовано Обновлено