Обработка входящей почты на PHP

    Хочу поделиться рецептом, как совместить php-скрипт и почтовый сервер Postfix для автоматической обработки входящей почты.

    С помощью такой связки мы собираем статистику по количеству возвратов bounce-писем:Undelivered Mail Returned to Sender.

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

    Как это сделать

    1. Для начала убедитесь, что у вас есть работающий почтовый сервер.

      Можно сделать отдельный почтовик в поддомене, достаточно внести 2 записи в DNS:
      rob.mydomain.ru. A ip-вашего-сервера
      rob.mydomain.ru. MX rob.mydomain.ru.
      

    2. Отредактируйте файл с алиасами /etc/aliases:
      добавьте туда строку:
      robot: "|php -q /путь/к/скрипту.php"
      robot — это название почтового ящика;
      /путь/к/скрипту.php — скрипт обрабатывающий входящие.

      после редактирования выполните команду newaliases

    3. в настройках postfix main.cf рекомендую добавить параметр:
      recipient_delimiter = +

      тогда в адресе можно будет кодировать дополнительную информацию:robot+someId@rob.mydomain.ru

      Все письма на такие адреса будет также обрабатывать наш скрипт.
      someId может быть идентификатором пользователя или любыми другими данными.
    4. создайте скрипт-обработчик писем:
      <?php
      
      /**
       * Скрипт для автоматической обработки входящих писем
       *
       * Все данные smtp-конверта письма RECIPIENT, SENDER и другие postfix 
       * передает через окружение $_ENV; полный перечень переменных:
       * http://www.postfix.org/local.8.html секция EXTERNAL COMMAND DELIVERY 
       */
      
      //текст сообщения считываем из STDIN
      $msg = file_get_contents("php://stdin");
      
      //отправитель письма
      $sender = getenv('SENDER');
      
      //получатель письма
      $recipient = getenv('RECIPIENT');
      
      //парсинг сообщения
      list($header, $body) = explode("\n\n", $msg, 2);
      
      //выделим строки с Subject: и From:
      $subject = '';
      $from = '';
      $headerArr = explode("\n", $header);
      foreach ($headerArr as $str) {
        if (strpos($str, 'Subject:') === 0) {
          $subject = $str;
        }
        if (strpos($str, 'From:') === 0) {
          $from = $str;
        }
      }
      
      //для отладки сохраняем полученное сообщение в лог:
      $logMsg = "=== MSG ===\n";
      $logMsg .= "SENDER: $sender\n";
      $logMsg .= "RECIPIENT: $recipient\n";
      $logMsg .= "$from\n";
      $logMsg .= "$subject\n\n";
      $logMsg .= "$msg\n";
      file_put_contents('/tmp/inb.log',$logMsg, FILE_APPEND);
      
    5. отправьте письмо на адрес robot@rob.mydomain.ru и загляните в лог /tmp/inb.log

    Готово!

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 43

      0
      Работает с любыми MTA?
        0
        Я полагаю что да.
        все MTA которые умеют работать с /etc/aliases
        0
        Вопрос не совсем по теме, но это то, что я надеялся тут найти и не нашел.

        А если почтовый сервер не у нас, а какой-нибудь Яндекс? Есть какие-нибудь конкретные признаки, которые позволят понять, что письмо не дошло? У меня есть сайт с рассылкой на 40000 адресов покупателей. Я смотрел возвраты о недоставке – каждый почтовик отправляет по-своему
          +1
          Отправляйте с адреса вида robot+userid@domain.com
          Если письмо вернулось, то с очень большой вероятностью это отлуп.
            0
            Остроумно :) Даже если не регистрировать на каждого пользователя ящик, нужно заводить на домене свой почтовый сервер. А мы решили снять с себя эту нагрузку и работаем по смтп с яндексом. Хотелось бы вышеуказанный функционал по имап реализовать
              +1
              Добавляйте X-Your-Header-Here и считывайте пришедшие на Return-Path письма. Так можно обрабатывать отлупы, да и не только :)
                +2
                Знак + существует специально для того чтобы отделить имя ящика от параметров.
                Т.е. ящик останется robot, но вы сможете проверять от какого userid пришло по хидеру To: в письме.
                Это умеет даже Яндекс, не говоря уже о профессиональных системах.
                  +3
                  Этот топик стал бездонной ямой для ликвидации моей безграмотности
                  +1
                  тогда domain.com привяжите к какому-нибудь сервису типа того-же Янедкса и включите получение всех писем на все адреса домена в один ящик. Потом скачивайте по ИМАП все письма из того ящика и проверяйте получателя. Правда, рано или поздно спамом вас накроет немного.
                +3
                основная почта у нас тоже обрабатывается Яндексом, а для
                возвратов мы завели поддомен bounce.mydom.ru

                и при отправлении письма, мы указываем Sender вида:
                bounce+name=gmail.con@bounce.mydom.ru

                но поле From не трогаем!

                у нас в скрипте рассылки есть такой код:
                ...
                    //Если почтовый сервер не может доставить письмо,
                    //он его возвращает отправителю.
                    //Мы же просим его все возвраты слать на адрес:
                    //bounce+...@bounce.mydom.ru
                    //и в этом адресе, кодируем получаетля, чтобы легче было парсить возврат
                    //(т.к. разные почтовые сервисы шлют их со своими текстами)
                    //Пример адреса для возврата: bounce+name=gmail.con@bounce.mydom.ru
                    //Эта техника называется VERP: http://en.wikipedia.org/wiki/Variable_envelope_return_path
                    //Поле Form: остается без изменений, поэтому на получателей это ни как не отражается.
                    $VERPAddr = str_replace('@', '=', $emailData['email']);
                    $mail->Sender = "bounce+$VERPAddr@bounce.mydom.ru";
                


                  0
                  Потрясающе. Можете показать кусок заголовков отправляемого письма? И это фича исключительно Яндекса?
                    0
                    Для рассылок пользователям мы используем свои smtp-сервера,
                    честно говоря не представляю как вам удается 40000 писем через сервера яндекса отправить…

                    Это правда возможно? ;-)
                      +1
                      Несколько ящиков и несколько дней терпения. У нас рассылка об акциях и обновлениях, так что не спешно и мы можем рассылать дней 5-7. Зато это совершенно бесплатно!

                      Яндекс разрешает что-то около 2500 писем в день с одного своего ящика. Самое сложное – не раздражать всяких мейл.ру, которые время от времени нас в спам укладывают. Но это довольно просто решается отправкой писем в их поддержку и со временем ситуация возникает все реже.
                        +1
                        А, да. Самым важным оказалось кодирование кириллицы в заголовках писем. За это в спам ложат в первую очередь. Как только закодировали – пошло нанмого лучше
                      0
                      и что действительно работает? этоже требует дополнительной настройки на серверах которым вы рассылаете.
                      А обычно отлупы идут на Return-path
                        0
                        либа (phpmailer) которой мы шлем автоматом добавляет Return-Path при изменении Sender — и если не ошибаюсь изменение Sender влияет на команду SMTP
                        MAIL FROM:
                           $useVerp = ($this->do_verp ? " XVERP" : "");
                            fputs($this->smtp_conn,"MAIL FROM:<" . $from . ">" . $useVerp . $this->CRLF);
                        


                        еще посмотрел в main.cf, у нас там такая штука есть:
                        smtpd_authorized_verp_clients = $mynetworks

                      0
                        +2
                        Все намного проще.
                        В заголовке для каждого письма указывайте уникальное поле «Errors-To:», куда почтовик будет отправлять отлупы. И обрабатывайте только адрес получателя, который со 100% гарантией связан с письмом отправителя (можете в базе хранить коды писем, а можете просто свой формат адреса сделать).

                        Например:

                        From: me@me.me
                        To: papa@mail.to
                        Errors-To: 12345.error@me.me
                        или даже на
                        Errors-To: papa.mail.to.error@me.me

                        всю почту на *.error@me.me заворачиваете на свой парсер и однозначно получаете информацию, что письмо не доставлено. В адрес можно включить любую информацию — дату, тему, получателя, код, фамилию и пр.
                          0
                          Знаете, мне уже столько вариантов, как это сделать предложили, что я начинаю терятся. Этот заголовок входит в стандарт? Его поддерживают все почтовики?
                            +1
                            Он вынесен из RFC-1341, но его по обратной совместимости поддерживают все почтовики. В новом RFC остался только «Reply-To:», который удобно использовать, чтобы не портить «From:», но если его не указывать, то адрес возьмут именно из «From:».
                            +1
                            вроде этот заголовок Errors-To считается deprecated
                          +1
                          так как вы собираете bounce?
                            0
                            чуть выше написал:
                            http://habrahabr.ru/blogs/php/126448/#comment_4170857

                            добавлю что парсить боунсы по тексту сообщения — сложно,
                            в том числе определить адресата, поэтому мы так не делаем :)

                            Просто кодируем адресата в «envelope sender»:
                            bounce+name=gmail.con@bounce.mydom.ru

                            и потом остается пропарсить:
                            $recipient = getenv('RECIPIENT'); //bounce+name=gmail.con@bounce.mydom.ru
                            чтобы понять, что доставка на адрес name@gmail.con не прошла.
                            +2
                            Не ожидал, что тема обработки bounce будет так интересна.
                            чуть позже соберу все мысли на этот счет и допишу
                              0
                              Любая работа с почтой интересна. Столько фич можно на ней завязать. Останавливает отсутствие высокоуровневых прослоек для работы с ней и общая разнородность (те же баунсы)
                                +2
                                а меня останавливает полная компрометация идеи электронной почты явлением спама и нежеланием сообщества внедрять всякие грабли для спаммеров типа SenderId, SPF и прочие.
                                  0
                                  Если вы мне назовете аналог, который не подвержен спаму – я вас расцелую.
                                    0
                                    вы же не написали, для каких целей вы используете почту.
                                    Если для простого информирования пользователей, то можно взять социальные сети (кросспостинг во все сразу и предложение зафрендить при оформлении заказа/на странице компании).
                                      0
                                      Эээ… Не буду вас целовать, сами виноваты :) В соцсетях сейчас спама еще больше, чем на почте. Но что нам остается – пользуемся и почтой и соцсетями.

                                      habrahabr.ru/blogs/php/126448/#comment_4170930
                                        0
                                        не надо целовать :)
                                        А как вам удаётся находить спаммеров в соцсетях? Я из своего личного опыта могу припомнить только одного за последнее время, который просил за него проголосовать где-то там. Пользуюсь фэйсбуком, немножко Вконтакте, Гугл+, Одноклассники, ЛинкедИн.
                                          0
                                          Давайте вспомним хотя бы трояны для вконтактика. Их время уже прошло, но еще случается.

                                          Понимаете, я их совсем не ищу :)
                              0
                              Как раз делал это сам месяц назад, с легким матерком ;)
                                0
                                Интересно, а что будет если на этот ваш email придёт n-ое количество писем с вложениями максимально допустимого размера?
                                  0
                                  > robot: "|php -q /путь/к/скрипту.php"

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

                                  Не разумнее ли валить все в ящик, и выгребать почту из него?
                                  В этом случае гораздо меньше риск потерm и использование ресурсов контролируемо.
                                    0
                                    Несколько лет назад разрабатывал систему, передача данных была через почту.
                                    Писем было относительно немного, около 20К в сутки.
                                    Все валились в ящик, откуда по крону ПХПшный скрипт их выгребал.
                                    Работало вполне устойчиво, в случае непредвиденностей — письма оставались в ящике и обрабатывались как только проблемы устранялись.
                                      0
                                      20к — это много писем. На некоторых дешёвых хостингах это превышает суточный лимит для одной учётной записи. Так что ваша работа заслуживает похвалы.
                                        0
                                        Ну… Почтовый сервер то наш :)
                                        А общий объем почты в то время был в среднем 400К писем в сутки, самый максимум (случилось однажды) 610К писем.

                                        Потом после оптимизаций в головах и бизнес-логике — через эту систему трафик подсократился раз в 5.
                                          0
                                          Посмотрел статистику, в тот день было 610К писем суммарным объемом 1,8Гб.
                                          Это так, для иллюстрации (ну и похвастаться ;)
                                      0
                                      Зависит от конкретных условий
                                      если в ящик валить и от туда парсить — то невиданный рейт и максимальный размер тоже может негативно отразиться на работоспособности сервера.

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

                                        Мое решение отпахало примерно 4 года.
                                          0
                                          Я не говорю что Ваше решение плохое.
                                          Оно просто слишком оптимистичное :)
                                        0
                                        Альтернативный способ для тех кому надо забирать почту из внешнего сервера или у кого нет доступа к конфигурации сервера: использовать PHP библиотеку php-imap.
                                          0
                                          Подскажите, как будет выглядеть алиас для перенаправления всей почты с определенного домена в скрипт.

                                          Only users with full accounts can post comments. Log in, please.