Валидация MX-записей: руководство разработчика

Leo
LeoFounder, BillionVerify

Валидация MX-записей: DNS-запросы, парсинг, обработка приоритетов и практики верификации доменов.

Cover Image for Валидация MX-записей: руководство разработчика

Каждое отправляемое вами письмо проходит через тщательно организованную сеть серверов, а записи Mail Exchange (MX) являются указателями, направляющими это путешествие. Понимание того, как валидировать MX-записи, является фундаментальным навыком для любого разработчика, создающего системы проверки электронной почты, контактные формы или приложения, которые собирают адреса электронной почты. Это всестороннее руководство исследует валидацию MX-записей от базовых концепций до продвинутых стратегий реализации, предоставляя вам знания для создания надежной проверки электронной почты в ваших приложениях.

Понимание MX-записей

Записи Mail Exchange — это DNS-записи, которые определяют, какие почтовые серверы отвечают за прием электронной почты от имени домена. Когда вы отправляете письмо на адрес user@example.com, ваш почтовый сервер должен знать, куда его доставить. MX-записи предоставляют эту информацию, указывая на почтовые серверы домена.

Как работают MX-записи

Когда отправляется письмо, отправляющий почтовый сервер выполняет DNS-запрос для поиска MX-записей домена получателя. Этот запрос возвращает одно или несколько имен почтовых серверов вместе со значениями приоритета, которые указывают порядок предпочтения.

Типичный запрос MX-записей для gmail.com может вернуть:

gmail.com.    MX    5     gmail-smtp-in.l.google.com.
gmail.com.    MX    10    alt1.gmail-smtp-in.l.google.com.
gmail.com.    MX    20    alt2.gmail-smtp-in.l.google.com.
gmail.com.    MX    30    alt3.gmail-smtp-in.l.google.com.
gmail.com.    MX    40    alt4.gmail-smtp-in.l.google.com.

Отправляющий сервер сначала пытается доставить письмо на сервер с наименьшим приоритетом (в данном случае приоритет 5). Если этот сервер недоступен, он пробует следующий уровень приоритета, и так далее. Такая избыточность обеспечивает доставку электронной почты даже когда отдельные серверы недоступны.

Компоненты MX-записи

Каждая MX-запись содержит две важные части информации:

Приоритет (Предпочтение)

Числовое значение, указывающее порядок, в котором следует пробовать почтовые серверы. Меньшие числа означают более высокий приоритет. Серверы с одинаковым приоритетом опрашиваются в случайном порядке, обеспечивая балансировку нагрузки.

Имя хоста почтового сервера

Полное доменное имя (FQDN) почтового сервера, который обрабатывает электронную почту для домена. Это имя хоста должно разрешаться в IP-адрес через A или AAAA запись.

Почему MX-записи важны для проверки электронной почты

Валидация MX-записей служит критической контрольной точкой в процессе проверки электронной почты:

Подтверждение существования домена

Если у домена нет MX-записей, он обычно не может получать электронную почту. Некоторые домены могут иметь резервную A-запись, но отсутствие MX-записей часто является сильным индикатором того, что домен не настроен для электронной почты.

Проверка инфраструктуры

Действительные MX-записи, которые разрешаются в работающие почтовые серверы, указывают на то, что домен имеет почтовую инфраструктуру. Это не гарантирует существование конкретного адреса, но подтверждает, что домен может получать электронную почту.

Обнаружение спама и мошенничества

Легитимные компании поддерживают правильные MX-записи. Подозрительные домены, используемые для спама или мошенничества, часто имеют неправильно настроенные или отсутствующие MX-записи.

Оптимизация производительности

Проверка MX-записей перед попыткой SMTP-верификации избегает потери времени на подключение к доменам, которые не могут получать электронную почту.

Реализация MX-запросов

Давайте рассмотрим, как реализовать валидацию MX-записей в различных программных средах.

Реализация в Node.js

Node.js предоставляет встроенное разрешение DNS через модуль dns:

const dns = require('dns').promises;

async function getMxRecords(domain) {
  try {
    const records = await dns.resolveMx(domain);

    // Сортировка по приоритету (сначала самый низкий)
    records.sort((a, b) => a.priority - b.priority);

    return {
      success: true,
      domain,
      records: records.map(r => ({
        exchange: r.exchange,
        priority: r.priority
      }))
    };
  } catch (error) {
    return {
      success: false,
      domain,
      error: error.code,
      message: getMxErrorMessage(error.code)
    };
  }
}

function getMxErrorMessage(code) {
  const messages = {
    'ENODATA': 'MX-записи для этого домена не найдены',
    'ENOTFOUND': 'Домен не существует',
    'ETIMEOUT': 'Тайм-аут DNS-запроса',
    'ESERVFAIL': 'DNS-сервер не смог ответить'
  };
  return messages[code] || 'Неизвестная ошибка DNS';
}

// Использование
const result = await getMxRecords('gmail.com');
console.log(result);

Реализация в Python

Модуль dns.resolver Python из библиотеки dnspython предоставляет всесторонние возможности DNS-запросов:

import dns.resolver
import dns.exception

def get_mx_records(domain):
    try:
        answers = dns.resolver.resolve(domain, 'MX')

        records = []
        for rdata in answers:
            records.append({
                'exchange': str(rdata.exchange).rstrip('.'),
                'priority': rdata.preference
            })

        # Сортировка по приоритету
        records.sort(key=lambda x: x['priority'])

        return {
            'success': True,
            'domain': domain,
            'records': records
        }

    except dns.resolver.NXDOMAIN:
        return {
            'success': False,
            'domain': domain,
            'error': 'NXDOMAIN',
            'message': 'Домен не существует'
        }

    except dns.resolver.NoAnswer:
        return {
            'success': False,
            'domain': domain,
            'error': 'NoAnswer',
            'message': 'MX-записи для этого домена не найдены'
        }

    except dns.exception.Timeout:
        return {
            'success': False,
            'domain': domain,
            'error': 'Timeout',
            'message': 'Тайм-аут DNS-запроса'
        }

# Использование
result = get_mx_records('gmail.com')
print(result)

Реализация в Go

Пакет net в Go предоставляет простые функции DNS-запросов:

package main

import (
    "fmt"
    "net"
    "sort"
)

type MxResult struct {
    Success bool
    Domain  string
    Records []MxRecord
    Error   string
}

type MxRecord struct {
    Exchange string
    Priority uint16
}

func getMxRecords(domain string) MxResult {
    records, err := net.LookupMX(domain)

    if err != nil {
        return MxResult{
            Success: false,
            Domain:  domain,
            Error:   err.Error(),
        }
    }

    if len(records) == 0 {
        return MxResult{
            Success: false,
            Domain:  domain,
            Error:   "No MX records found",
        }
    }

    // Сортировка по приоритету
    sort.Slice(records, func(i, j int) bool {
        return records[i].Pref < records[j].Pref
    })

    result := MxResult{
        Success: true,
        Domain:  domain,
        Records: make([]MxRecord, len(records)),
    }

    for i, r := range records {
        result.Records[i] = MxRecord{
            Exchange: r.Host,
            Priority: r.Pref,
        }
    }

    return result
}

func main() {
    result := getMxRecords("gmail.com")
    fmt.Printf("%+v\n", result)
}

Продвинутые техники MX-валидации

Базовые MX-запросы подтверждают существование записей, но всесторонняя валидация электронной почты требует более глубокого анализа.

Валидация подключения к почтовым серверам

MX-записи указывают на имена хостов, которые должны разрешаться в IP-адреса. Проверьте, что почтовые серверы действительно доступны:

const dns = require('dns').promises;
const net = require('net');

async function validateMxConnectivity(domain) {
  // Получение MX-записей
  const mxResult = await getMxRecords(domain);

  if (!mxResult.success) {
    return mxResult;
  }

  // Валидация каждого почтового сервера
  const validatedRecords = [];

  for (const record of mxResult.records) {
    const validation = await validateMailServer(record.exchange);
    validatedRecords.push({
      ...record,
      ...validation
    });
  }

  return {
    success: true,
    domain,
    records: validatedRecords,
    hasReachableServer: validatedRecords.some(r => r.reachable)
  };
}

async function validateMailServer(hostname) {
  try {
    // Разрешение имени хоста в IP
    const addresses = await dns.resolve4(hostname);

    if (addresses.length === 0) {
      return { reachable: false, error: 'Нет A-записи' };
    }

    // Тест подключения к порту 25
    const connected = await testConnection(addresses[0], 25);

    return {
      reachable: connected,
      ip: addresses[0],
      error: connected ? null : 'Соединение отклонено'
    };
  } catch (error) {
    return {
      reachable: false,
      error: error.message
    };
  }
}

function testConnection(host, port, timeout = 5000) {
  return new Promise((resolve) => {
    const socket = new net.Socket();

    socket.setTimeout(timeout);

    socket.on('connect', () => {
      socket.destroy();
      resolve(true);
    });

    socket.on('timeout', () => {
      socket.destroy();
      resolve(false);
    });

    socket.on('error', () => {
      resolve(false);
    });

    socket.connect(port, host);
  });
}

Обработка резервной A-записи

Когда MX-записи отсутствуют, стандарты электронной почты (RFC 5321) указывают, что A-запись домена должна использоваться в качестве резервной. Реализуйте эту резервную логику в вашей валидации:

async function getMailServers(domain) {
  // Сначала пробуем MX-записи
  try {
    const mxRecords = await dns.resolveMx(domain);

    if (mxRecords.length > 0) {
      return {
        type: 'MX',
        servers: mxRecords.sort((a, b) => a.priority - b.priority)
      };
    }
  } catch (error) {
    if (error.code !== 'ENODATA') {
      throw error;
    }
  }

  // Резервная A-запись
  try {
    const aRecords = await dns.resolve4(domain);

    if (aRecords.length > 0) {
      return {
        type: 'A_FALLBACK',
        servers: [{ exchange: domain, priority: 0 }],
        warning: 'Используется резервная A-запись - MX-записи не найдены'
      };
    }
  } catch (error) {
    if (error.code !== 'ENODATA') {
      throw error;
    }
  }

  return {
    type: 'NONE',
    servers: [],
    error: 'Почтовые серверы для домена не найдены'
  };
}

Обнаружение нулевых MX-записей

RFC 7505 определяет "нулевые MX" записи, которые явно указывают, что домен не принимает электронную почту. Эти записи имеют одну MX-запись с приоритетом 0 и пустым именем хоста ("."):

function hasNullMx(mxRecords) {
  if (mxRecords.length === 1) {
    const record = mxRecords[0];
    if (record.priority === 0 &&
        (record.exchange === '.' || record.exchange === '')) {
      return true;
    }
  }
  return false;
}

async function validateDomainMx(domain) {
  const mxResult = await getMxRecords(domain);

  if (!mxResult.success) {
    return mxResult;
  }

  if (hasNullMx(mxResult.records)) {
    return {
      success: false,
      domain,
      error: 'NULL_MX',
      message: 'Домен явно не принимает электронную почту'
    };
  }

  return mxResult;
}

Кэширование MX-запросов

DNS-запросы добавляют задержку к каждой проверке. Реализуйте кэширование для улучшения производительности:

class MxCache {
  constructor(ttlMs = 3600000) { // TTL по умолчанию 1 час
    this.cache = new Map();
    this.ttl = ttlMs;
  }

  get(domain) {
    const entry = this.cache.get(domain.toLowerCase());

    if (!entry) return null;

    if (Date.now() > entry.expiry) {
      this.cache.delete(domain.toLowerCase());
      return null;
    }

    return entry.data;
  }

  set(domain, data) {
    this.cache.set(domain.toLowerCase(), {
      data,
      expiry: Date.now() + this.ttl
    });
  }

  // Учитываем значения DNS TTL, когда доступны
  setWithTtl(domain, data, ttlSeconds) {
    const ttlMs = Math.min(ttlSeconds * 1000, this.ttl);
    this.cache.set(domain.toLowerCase(), {
      data,
      expiry: Date.now() + ttlMs
    });
  }
}

const mxCache = new MxCache();

async function getMxRecordsCached(domain) {
  const cached = mxCache.get(domain);

  if (cached) {
    return { ...cached, fromCache: true };
  }

  const result = await getMxRecords(domain);

  if (result.success) {
    mxCache.set(domain, result);
  }

  return { ...result, fromCache: false };
}

Распространенные паттерны MX-записей

Понимание распространенных конфигураций MX помогает вам интерпретировать результаты валидации и выявлять потенциальные проблемы.

Основные почтовые провайдеры

Распознавание MX-паттернов основных провайдеров может помочь идентифицировать бесплатные адреса электронной почты:

const knownProviders = {
  'google': [
    'gmail-smtp-in.l.google.com',
    'googlemail-smtp-in.l.google.com',
    'aspmx.l.google.com'
  ],
  'microsoft': [
    'outlook-com.olc.protection.outlook.com',
    'mail.protection.outlook.com'
  ],
  'yahoo': [
    'mta5.am0.yahoodns.net',
    'mta6.am0.yahoodns.net',
    'mta7.am0.yahoodns.net'
  ],
  'protonmail': [
    'mail.protonmail.ch',
    'mailsec.protonmail.ch'
  ]
};

function identifyEmailProvider(mxRecords) {
  const exchanges = mxRecords.map(r => r.exchange.toLowerCase());

  for (const [provider, patterns] of Object.entries(knownProviders)) {
    for (const pattern of patterns) {
      if (exchanges.some(ex => ex.includes(pattern.toLowerCase()))) {
        return provider;
      }
    }
  }

  return 'unknown';
}

Обнаружение Google Workspace

Домены Google Workspace (ранее G Suite) используют почтовые серверы Google, но не являются бесплатными учетными записями:

function isGoogleWorkspace(domain, mxRecords) {
  const isGoogleMx = mxRecords.some(r =>
    r.exchange.toLowerCase().includes('google') ||
    r.exchange.toLowerCase().includes('googlemail')
  );

  // Проверка, что домен не является известным потребительским доменом Google
  const googleConsumerDomains = ['gmail.com', 'googlemail.com'];
  const isConsumerDomain = googleConsumerDomains.includes(domain.toLowerCase());

  return isGoogleMx && !isConsumerDomain;
}

Обнаружение самостоятельно размещенной почты

Домены, которые размещают собственную электронную почту, часто имеют MX-записи, указывающие на поддомены:

function isSelfHosted(domain, mxRecords) {
  const domainParts = domain.toLowerCase().split('.');
  const baseDomain = domainParts.slice(-2).join('.');

  return mxRecords.some(r => {
    const exchange = r.exchange.toLowerCase();
    return exchange.includes(baseDomain) &&
           !isKnownProvider(exchange);
  });
}

function isKnownProvider(exchange) {
  const providers = ['google', 'microsoft', 'yahoo', 'outlook', 'protonmail'];
  return providers.some(p => exchange.includes(p));
}

MX-валидация в пайплайнах проверки электронной почты

MX-валидация является одним из шагов в комплексном процессе проверки электронной почты. Понимание ее роли помогает создавать эффективные пайплайны верификации.

Порядок верификации

MX-валидация обычно происходит на ранней стадии пайплайна верификации:

async function verifyEmail(email) {
  // 1. Валидация синтаксиса (самая быстрая, без сети)
  const syntaxResult = validateEmailSyntax(email);
  if (!syntaxResult.valid) {
    return { valid: false, reason: 'invalid_syntax', details: syntaxResult };
  }

  const domain = email.split('@')[1];

  // 2. Валидация MX-записей (быстрый DNS-запрос)
  const mxResult = await validateMxRecords(domain);
  if (!mxResult.valid) {
    return { valid: false, reason: 'no_mx_records', details: mxResult };
  }

  // 3. Дополнительные проверки (одноразовые, ролевые и т.д.)
  const domainCheck = await checkDomainReputation(domain);
  if (domainCheck.isDisposable) {
    return { valid: true, risky: true, reason: 'disposable_domain' };
  }

  // 4. SMTP-верификация (самая медленная, самая тщательная)
  const smtpResult = await verifySmtp(email, mxResult.records);

  return {
    valid: smtpResult.exists,
    deliverable: smtpResult.deliverable,
    mxRecords: mxResult.records,
    provider: mxResult.provider
  };
}

Параллельные MX-запросы

При проверке нескольких писем, распараллеливайте MX-запросы для разных доменов:

async function verifyEmailsBatch(emails) {
  // Группировка писем по доменам
  const emailsByDomain = {};

  for (const email of emails) {
    const domain = email.split('@')[1];
    if (!emailsByDomain[domain]) {
      emailsByDomain[domain] = [];
    }
    emailsByDomain[domain].push(email);
  }

  // Запрос MX-записей для всех доменов параллельно
  const domains = Object.keys(emailsByDomain);
  const mxResults = await Promise.all(
    domains.map(domain => getMxRecordsCached(domain))
  );

  // Сопоставление результатов с доменами
  const mxByDomain = {};
  domains.forEach((domain, index) => {
    mxByDomain[domain] = mxResults[index];
  });

  // Обработка писем с MX-данными
  const results = [];

  for (const email of emails) {
    const domain = email.split('@')[1];
    const mx = mxByDomain[domain];

    if (!mx.success) {
      results.push({ email, valid: false, reason: 'invalid_domain' });
      continue;
    }

    // Продолжение с SMTP-верификацией, используя кэшированные MX-данные
    const smtpResult = await verifySmtp(email, mx.records);
    results.push({ email, ...smtpResult });
  }

  return results;
}

Обработка ошибок и граничных случаев

Надежная MX-валидация корректно обрабатывает различные условия ошибок.

Обработка тайм-аутов DNS

Сетевые проблемы могут привести к зависанию DNS-запросов. Реализуйте обработку тайм-аутов:

async function getMxRecordsWithTimeout(domain, timeoutMs = 10000) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('DNS_TIMEOUT')), timeoutMs);
  });

  try {
    const result = await Promise.race([
      getMxRecords(domain),
      timeoutPromise
    ]);
    return result;
  } catch (error) {
    if (error.message === 'DNS_TIMEOUT') {
      return {
        success: false,
        domain,
        error: 'TIMEOUT',
        message: 'Тайм-аут DNS-запроса',
        retryable: true
      };
    }
    throw error;
  }
}

Обработка недействительных доменов

Обрабатывайте синтаксически недействительные домены до попытки DNS-запросов:

function isValidDomain(domain) {
  if (!domain || typeof domain !== 'string') {
    return false;
  }

  // Проверка длины
  if (domain.length > 253) {
    return false;
  }

  // Проверка на допустимые символы и структуру
  const domainRegex = /^(?!-)[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;

  if (!domainRegex.test(domain)) {
    return false;
  }

  // Проверка длины каждого лейбла
  const labels = domain.split('.');
  for (const label of labels) {
    if (label.length > 63) {
      return false;
    }
  }

  return true;
}

async function validateDomainMxSafe(domain) {
  if (!isValidDomain(domain)) {
    return {
      success: false,
      domain,
      error: 'INVALID_DOMAIN',
      message: 'Формат домена недействителен'
    };
  }

  return await getMxRecordsWithTimeout(domain);
}

Обработка временных сбоев DNS

Сбои DNS могут быть временными. Реализуйте логику повторных попыток с экспоненциальной задержкой:

async function getMxRecordsWithRetry(domain, maxRetries = 3) {
  const delays = [1000, 2000, 4000];

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const result = await getMxRecordsWithTimeout(domain);

    // Не повторять для окончательных сбоев
    if (result.success ||
        result.error === 'NXDOMAIN' ||
        result.error === 'ENOTFOUND') {
      return result;
    }

    // Повторить для временных сбоев
    if (result.retryable && attempt < maxRetries - 1) {
      await sleep(delays[attempt]);
      continue;
    }

    return result;
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Соображения безопасности

MX-валидация вводит соображения безопасности, которые разработчики должны учитывать.

Предотвращение подмены DNS

Стандартные DNS-запросы не зашифрованы и уязвимы для подмены. Рассмотрите использование DNS через HTTPS (DoH) для критичных приложений:

const https = require('https');

async function getMxRecordsDoH(domain) {
  const url = `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(domain)}&type=MX`;

  return new Promise((resolve, reject) => {
    https.get(url, {
      headers: { 'Accept': 'application/dns-json' }
    }, (res) => {
      let data = '';

      res.on('data', chunk => data += chunk);

      res.on('end', () => {
        try {
          const response = JSON.parse(data);

          if (response.Status !== 0) {
            resolve({
              success: false,
              domain,
              error: 'DNS_ERROR',
              status: response.Status
            });
            return;
          }

          const records = (response.Answer || [])
            .filter(a => a.type === 15)
            .map(a => {
              const [priority, exchange] = a.data.split(' ');
              return {
                priority: parseInt(priority),
                exchange: exchange.replace(/\.$/, '')
              };
            })
            .sort((a, b) => a.priority - b.priority);

          resolve({
            success: records.length > 0,
            domain,
            records
          });
        } catch (error) {
          reject(error);
        }
      });
    }).on('error', reject);
  });
}

Ограничение скорости DNS-запросов

Предотвращайте злоупотребления, ограничивая скорость DNS-запросов:

class DnsRateLimiter {
  constructor(maxQueriesPerSecond = 100) {
    this.tokens = maxQueriesPerSecond;
    this.maxTokens = maxQueriesPerSecond;
    this.lastRefill = Date.now();
  }

  async acquire() {
    this.refillTokens();

    if (this.tokens > 0) {
      this.tokens--;
      return true;
    }

    // Ожидание доступности токена
    await sleep(1000 / this.maxTokens);
    return this.acquire();
  }

  refillTokens() {
    const now = Date.now();
    const elapsed = now - this.lastRefill;
    const tokensToAdd = (elapsed / 1000) * this.maxTokens;

    this.tokens = Math.min(this.maxTokens, this.tokens + tokensToAdd);
    this.lastRefill = now;
  }
}

const dnsLimiter = new DnsRateLimiter(50);

async function getMxRecordsRateLimited(domain) {
  await dnsLimiter.acquire();
  return getMxRecords(domain);
}

Использование BillionVerify для MX-валидации

Хотя самостоятельная реализация MX-валидации имеет образовательную ценность, профессиональные сервисы проверки электронной почты, такие как BillionVerify, обрабатывают MX-валидацию как часть комплексной проверки электронной почты.

Преимущества использования API проверки электронной почты

Комплексные проверки

API проверки электронной почты BillionVerify сочетает MX-валидацию с проверкой синтаксиса, SMTP-верификацией, обнаружением одноразовых адресов и многое другое в одном вызове API. Это исключает необходимость поддержки нескольких систем валидации.

Оптимизированная инфраструктура

Профессиональные сервисы поддерживают глобально распределенные DNS-резолверы, обрабатывают кэширование в масштабе и оптимизируют производительность для миллионов проверок.

Непрерывные обновления

Конфигурации почтовых серверов постоянно меняются. Сервисы проверки электронной почты непрерывно обновляют свои базы данных известных провайдеров, одноразовых доменов и паттернов почтовых серверов.

Пример интеграции API

async function verifyEmailWithBillionVerify(email) {
  const response = await fetch('https://api.billionverify.com/v1/verify', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.BV_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ email })
  });

  const result = await response.json();

  // MX-информация включена в ответ
  console.log('MX действительна:', result.mx_found);
  console.log('Домен действителен:', result.domain_valid);
  console.log('Доставляема:', result.is_deliverable);

  return result;
}

Заключение

Валидация MX-записей является фундаментальным компонентом проверки электронной почты, который подтверждает, что домен может получать электронную почту, прежде чем пытаться выполнить более ресурсоемкие проверки. Реализуя правильную MX-валидацию, вы можете быстро отфильтровать недействительные домены, оптимизировать производительность верификации и создавать более надежные приложения для обработки электронной почты.

Ключевые выводы для валидации MX-записей:

  1. Всегда проверяйте MX-записи перед попыткой SMTP-верификации, чтобы сэкономить время и ресурсы
  2. Обрабатывайте резервную A-запись согласно стандартам RFC для доменов без MX-записей
  3. Реализуйте кэширование для уменьшения накладных расходов DNS-запросов при повторных валидациях
  4. Распознавайте распространенные паттерны для идентификации почтовых провайдеров и потенциальных рисков
  5. Обрабатывайте ошибки корректно с тайм-аутами, повторными попытками и правильными сообщениями об ошибках

Независимо от того, создаете ли вы пользовательскую систему проверки электронной почты или интегрируетесь с сервисом вроде BillionVerify, понимание MX-записей помогает вам создавать лучшую обработку электронной почты в ваших приложениях. Начните реализацию MX-валидации сегодня и сделайте первый шаг к комплексной проверке электронной почты.

Команды, использующие Instantly или Smartlead, улучшают доставляемость, очищая списки с BillionVerify перед каждой кампанией.

Сравните BillionVerify с ZeroBounce по точности и скорости, прежде чем выбирать поставщика верификации.

Leo
LeoFounder, BillionVerify
Аналитика проверки Email

Начните проверку сегодня

Начните проверять email с BillionVerify уже сегодня. Получите 100 бесплатных кредитов при регистрации — кредитная карта не требуется. Присоединяйтесь к тысячам компаний, улучшающих ROI email-маркетинга с помощью точной проверки email.

Кредитная карта не требуется · 100+ бесплатных кредитов в день · Начать за 30 секунд

99.9%
Точность
Real-time
Скорость API
$0.00014
За email
100/day
Бесплатно навсегда