Уж сколько раз твердили Миру… Существует давний и, вероятно, нескончаемый спор о том, какой именно регуляркой правильно и нужно проверять поле email пользователя.

Да, проверять регуляркой действительно нужно. Но ведь наши продукты работают в Сети. Так почему бы не использовать её настоящую мощь?

К тому-же нередко бывают ситуации, когда пользователи реально ошибаются при вводе адреса email (в том числе и в домене). Ну или, в поле email вводят любую возможную «Хабракадабру», что легко пролетит через regexp, но никак не может быть почтой, потому что даже домена такого не существует в природе :)

Кстати, на этом вот нюансе мы буквально только что подзалетели: суть в том что на сайте, поднятом на одной, довольно популярной CMS-ке у нас почему-то прекратили идти email-уводемления.

Причиной, как выяснилось, стало попадание адреса рассыльщика в спам.

Причин было несколько:

  1. CMS довольно популярная, а, стало быть, и регистрирующихся ботом-спамеров по неё немало. И что интереснее — в настройках можно (и многие так, к слову, и делают) — отключают проверку email. В этом случае сюда можно (и так большинство ботов и делает) вводить любую белиберду
  2. Тексты писем не были переписаны со стандартных.

Итого: спамеры массово лезли регистрироваться, кидали скрипту левые email-ы, куда мы пытались отправлять письма. Фильтр же спама видел что с нашего email-а идёт ряд писем, с текстами, что он уже видел много раз с других email-адресов, и при этом немалое их количество валится на несуществующие email-адреса.

В общем почтовый адрес периодически подпадал под спам.

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

Собственно суть проверки довольно проста: при регистрации, на стадии валидации данных пользователя мы отщепляем домен от email-а, и смотрим что там есть по MX-ам.

Сложно? На самом деле нет. Но позволяет существенно снизить нагрузку на почтовые службы. И, кстати, гораздо реже попадать в спам-листы (ведь отсылка большого числа писем на несуществующие почтовые адреса — один из признаков спама).

На PHP, как ни странно, сделать это довольно просто:

$email ="11@sdlkfjsdl.co.uk";
$domain = substr(strrchr($email, "@"), 1);
$res = getmxrr($domain, $mx_records, $mx_weight);
if (false == $res || 0 == count($mx_records) || (1 == count($mx_records) && ($mx_records[0] == null  || $mx_records[0] == "0.0.0.0" ) ) ){
//Проверка не пройдена - нормальные mx-записи не обнаружены
	echo "No MX for domain: $domain";
}else{
//Проверка пройдена, живая MX-запись на домене есть, и почта на нём работает
	echo "It seems that we have qualify MX-records for domain: $domain";
}

Поясню по довольно «монструозному» if-у. Дело в том, что в документации к функции getmxrr были комментарии с упоминаниями про не совсем корректное его поведение. И хотя на php7.1 мне их обнаружить не удалось — лишняя проверка — не лишняя :)

На ruby это делается схожим образом:

domain = invite.email.split('@').last.mb_chars.downcase.to_s.force_encoding("UTF-8")
#На случай, если домен русскоязычный. Точнее уже не совсем помню зачем преобразовывал в UTF-8, но видимо нечто вылетало

mail_servers = Resolv::DNS.open.getresources(domain, Resolv::DNS::Resource::IN::MX)
if mail_servers.empty?
   #Нет MX-серверов. Нечего и пытаться сюда слать письма
   false
else
   true
end

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

Комментарии (22)


  1. andreymal
    28.12.2018 04:07

    Да, проверять регуляркой действительно нужно.

    Странно, я постоянно читаю, что проверять регуляркой НЕ нужно https://habr.com/post/175375/


    1. ZiNTeR Автор
      28.12.2018 05:12

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


      1. powerman
        28.12.2018 09:53

        А смысл? Если юзер указал некорректный домен, то отправленное письмо не придёт никуда — оно вообще не отправится дальше вашего собственного SMTP, который вас банить не должен. Подозреваю, что проблему вызвала попытка отправлять почту через SMTP gmail или подобного сервиса… ну, тут скорее ССЗБ и проблема решается использованием собственного SMTP, а не валидацией MX.


        1. Alexmaru
          28.12.2018 19:41

          чтобы сказать о проблеме юзеру сразу, естественно.


          1. powerman
            28.12.2018 19:54
            +1

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


    1. ProRunner
      28.12.2018 09:06

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

      Фильтр же спама видел что с нашего email-а идёт ряд писемю… и при этом немалое их количество валится на несуществующие email-адреса.
      В общем почтовый адрес периодически подпадал под спам.


  1. aleksandy
    28.12.2018 07:16

    Тема обсасывалась уже не единожды. Единственно верный способ проверить валидность email-а — это отправить на него письмо и не получить отлуп от почтового сервера. Остальные способы — лишь некоторая вероятность того, что email валидный.


    1. miksoft
      28.12.2018 08:15

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


  1. NeLexa
    28.12.2018 09:08
    +2

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


    1. MihaOo
      28.12.2018 14:52
      +1

      Стоит так же упомянуть что страница мануала для getmxrr четко говорит: Эта функция не должна использоваться для проверки адреса., что как бы намекает.


      1. ZiNTeR Автор
        28.12.2018 14:56

        Мы проверяем не то, что существует конкрентный email, а то, что домен, у которого данный email был прописан — он в принципе имеет mail сервер для обработки писем…


    1. ZiNTeR Автор
      28.12.2018 14:53

      Отвечают не сами сервера, а DNS-ы. И если вы не отрезолвисились по DNS на сервер MX — стало быть никакого письма вы отправить не сможете точно.


  1. mxms
    28.12.2018 13:34

    Единственный надёжный способ проверить существование адреса электронной почты это отправка туда письма с кодом подтверждения. Наличие MX у домена (по хорошему надо проверять ещё и его разрешение в IP или, что лучше, наличие слушающего по этому адресу 25 порта) не гарантирует существование / функционирование ящика.
    Всё остальное компромисс.


  1. Fedcomp
    28.12.2018 14:42

    domain = invite.email.split('@').last.mb_chars.downcase.to_s.force_encoding("UTF-8")
    #На случай, если домен русскоязычный. Точнее уже не совсем помню зачем преобразовывал в UTF-8, но видимо нечто вылетало
    


    Без mb_chars не будет downcase для русских символов работать. Скорее всего после этого преобразование в обычную рубишную строку обратно.


    1. ZiNTeR Автор
      28.12.2018 14:59

      Это ясно. Я просто не помню зачем мне была нужна кодировка utf-8. Правда так хуже точно не будет :)


      1. Fedcomp
        28.12.2018 15:06

        на самом деле наличие force_encoding скорее всего говорит о том что где то что то не так и лучше избавиться от проблем с кодировкой на архитектурном уровне.


  1. vdo2000
    28.12.2018 14:50

    А что только домены проверяются? Можно проверять и сам ящик без отправки письма на него?


    1. ZiNTeR Автор
      28.12.2018 14:52

      Неплохая идея. Но тогда вам нужно будет устанавливать полноценное соединение через c почтовым серверов, обменяться приветствиями, потом заявить что «я хочу отправить письмо такому-то», и если получаете ответ «да, это возможно» — обрываете соединение, отправляете письмо стандартными средствами. Но это довольно небыстро по времени :)


  1. YourChief
    29.12.2018 01:46
    +1

    Этот способ также некорректный.

    Случай первый:
    RFC 974 определяет порядок маршрутизации почты:

    There is one other special case. If the response contains an answer
    which is a CNAME RR, it indicates that REMOTE is actually an alias
    for some other domain name. The query should be repeated with the
    canonical domain name.

    В случае, если домен является алиасом, ваш код посчитает, что это невалидный домен.

    Случай второй:
    Раздел 5 RFC5321 определяет корректное поведение отправителя почты:
    The lookup first attempts to locate an MX record associated with the
    name. If a CNAME record is found, the resulting name is processed as
    if it were the initial name. If a non-existent domain error is
    returned, this situation MUST be reported as an error. If a
    temporary error is returned, the message MUST be queued and retried
    later (see Section 4.5.4.1). If an empty list of MXs is returned,
    the address is treated as if it was associated with an implicit MX
    RR, with a preference of 0, pointing to that host.
    If MX records are
    present, but none of them are usable, or the implicit MX is unusable,
    this situation MUST be reported as an error.

    То есть в случае отсутствия MX-записи доставка нормальным образом по стандарту происходит по A-записи домена.

    Кроме того, почта может быть доставлена по SRV-записям, но предложенный Вами код этого конечно же не учитывает.

    Вообще говоря, домены электронной почты не обязаны быть доступны постоянно. Стандарт предлагает такую стратегию доставки:
    The sender MUST delay retrying a particular destination after one
    attempt has failed. In general, the retry interval SHOULD be at
    least 30 minutes; however, more sophisticated and variable strategies
    will be beneficial when the SMTP client can determine the reason for
    non-delivery.

    Retries continue until the message is transmitted or the sender gives
    up; the give-up time generally needs to be at least 4-5 days. It MAY
    be appropriate to set a shorter maximum number of retries for non-
    delivery notifications and equivalent error messages than for
    standard messages. The parameters to the retry algorithm MUST be
    configurable.


    То есть чтобы убедиться, что домен мёртвый, его нужно наблюдать хотя бы 4-5 дней.

    В общем, такой способ проверки по большому счёту — мусор. Сложившийся стандарт в индустрии по этому вопросу — отправить письмо с подтверждением адреса и до подтверждения не включать в списки рассылок.


    1. ZiNTeR Автор
      30.12.2018 18:14

      Ну на самом деле, по крайней мере в современной практике получается так, что если не получилось отправить письмо прямо сейчас — значит почтовый сервис де-факто не работает.
      Но вы натолкнули меня на мысль: отсутствие MX-записи по факту ещё не означает полный отлуп (хоть в немалом количестве случае это, скорее всего и будет), но является поводом призадуматься.
      Поэтому да, логичнее не увидев MX-записи, посмотреть в A и в CNAME — если не будет и их — тогда это гарантированный отлуп.
      Если есть CNAME — берём адрес или домен из него. и Повторяем цикл.
      Как только по циклу дошли до ip-адреса — смотрим наличие 25-го порта. Если он открыт и представляется — тогда ок, почта есть.
      Если нет — отлуп.
      Хотя да, думаю что вы правы — это довольно геморройный уже получается с точки зрения написания способ проверки.
      Но по крайней мере можно сказать одно: нет mx, cname и a-записей — это однозначный отлуп и почты наверняка нет.


  1. Tangeman
    29.12.2018 01:55

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

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

    Что касается регулярок — помню был у клиента адрес типа ..xx|zz..@ — и он оказался работающим (попал в базу до того как email стал проверяться на фронтэнде и прошел валидацию через отправку кода).

    Так что, как уже говорили раньше, единственный способ убедится в реальности адреса — это получить от пользователя подтверждение после отправки ему кода валидации или URL (причём последний должен быть с явным подтверждением после нажатия, или его могут случайно «подтвердить» антивирусы и/или антиспамы).

    Единственное что можно улучшить — это интегрировать валидацию в процесс регистрации (или смены адреса), т.е. делать её немедленно после подтверждения email, и говорить пользователю если адрес получил отлуп. Но этот путь тернист, ибо ведет к потенциальной DoS, а также может дать сбой если сервер временно недоступен.


  1. tendium
    29.12.2018 22:54

    Интересно, что к функции getmxrr() есть примечание в документации:

    This function should not be used for the purposes of address verification. Only the mailexchangers found in DNS are returned, however, according to » RFC 2821 when no mail exchangers are listed, hostname itself should be used as the only mail exchanger with a priority of 0.