Отправка писем php imap. Вытаскиваем письма из почты. Работаем с протоколом IMAP. Работаем с телом письма

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

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

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

Параметры URL для вызова необходимых функций:

  • func — тип функции, необходимые (например: читать электронную почту, просматривать почтовый ящик, удалить сообщение)
  • folder - имя папки почтового ящика для подключения (например: Входящие, Отправленные, спам)
  • uid — уникальный идентификатор электронной почты

Параметры можно получить с помощью $ _GET и switch могут быть использованы для вызова соответствующих действий.

Подключение к серверу IMAP

Чтобы установить соединение с сервером IMAP, мы используем функцию imap_connect() , как показано ниже:

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

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

Вот несколько строк для папки Входящие почтовых служб:

  • Gmail {imap.gmail.com: 993/imap/ssl} INBOX
  • Yahoo {imap.mail.yahoo.com: 993/imap/ssl} INBOX
  • AOL {imap.aol.com: 993/imap/ssl} INBOX

На некоторых серверах не включен SSL, в этом случае Вам нужно будет опустить «SSL» из строки. Другие серверы могут использовать собственные сертификаты, в которых вы должны включить «NOVALIDATE-CERT».

С открытым подключением к почтовому серверу, мы можем теперь взглянуть на функции, используемые для следующих видов деятельности:

  • Отображение списка папки почтового ящика в вашей учетной записи электронной почты
  • Отображение списка сообщений электронной почты в папке
  • Просмотр содержимого электронной почты автора

Папки электронной почты

Входящие , Отправленные , Мусор и Спам — эти папки видны практически в каждой учетной записи электронной почты, и пользователи часто могут создавать собственные папки. Для того, чтобы просмотреть сообщения в этих папках, мы должны изменить нашу строку подключения. Например, применить «INBOX» (Входящие) в строке пути ранее. Если нужно, подключиться к папке для спама, используйте что-то вроде «Spam» (Спам) вместо этого. Но хотя это может быть перечислено как Спам в учетную запись электронной почты, если смотреть через почтовый клиент, настоящее название папок в фоне может быть разным. Мы можем перечислить все имеющиеся папки в учетной записи с помощью imap_list() .

"; foreach ($folders as $folder) { $folder = str_replace("{imap.gmail..php?folder=" . $folder . "&func=view">" . $folder . ""; } echo "";

Мы должны передать дескриптор соединения, полученные с imap_open() в качестве исходного параметра imap_list() . Мы также должны пройти путь (без папки, например, «INBOX»). В качестве третьего параметра запрос всех доступных папок.

Уведомление о сообщении

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

Сначала нужно получить количество сообщений, доступных с помощью imap_num_msg() . Затем мы можем использовать функцию imap_header() , чтобы получить информацию для заголовка каждого сообщения.

Скажем, если мы хотим последние 20 писем:

($numMessages - 20); $i--) { $header = imap_header($imap, $i); $fromInfo = $header->from; $replyInfo = $header->reply_to; $details = array("fromAddr" => (isset($fromInfo->mailbox) && isset($fromInfo->host)) ? $fromInfo->mailbox . "@" . $fromInfo->host: "", "fromName" => (isset($fromInfo->personal)) ? $fromInfo->personal: "", "replyAddr" => (isset($replyInfo->mailbox) && isset($replyInfo->host)) ? $replyInfo->mailbox . "@" . $replyInfo->host: "", "replyName" => (isset($replyTo->personal)) ? $replyto->personal: "", "subject" => (isset($header->subject)) ? $header->subject: "", "udate" => (isset($header->udate)) ? $header->udate: ""); $uid = imap_uid($imap, $i); echo "

    "; echo "
  • From:" . $details["fromName"]; echo " " . $details["fromAddr"] . "
  • "; echo "
  • Subject: " . $details["subject"] ..php?folder=" . $folder . "&uid=" . $uid ..php?folder=" . $folder . "&uid=" . $uid . "&func=delete">Delete
  • "; echo "
"; }

Соединения $Imap должны быть открыты в нужной папке. Мы можем затем пройти через последние 20 писем с использованием числа сообщений, полученных imap_num_msg() . Связь и номер электронной почты даются imap_header() , чтобы получить информацию о заголовке, который затем может быть перинят за интересную информацию от отправителя, адрес электронной почты, имя, тема и т.д

Обратите внимание, что номер электронной почты от общего количества сообщений, не является уникальным идентификатором для сообщения. Если у вас есть 100 писем в вашем почтовом ящике, то последнее число будет 100, предыдущая будет 99, и так далее. Если вы удалите сообщение под номером 100, а затем получаете новое сообщение, его номер будет 100.

Чтобы приступить к следующим действиям, нужно получить уникальный идентификатор для электронной почты. Каждое письмо имеет уникальный идентификатор, называемый UID, который мы можем получить, предоставляя электронную почту, функция для номера imap_uid() UID-уникальный и не будет меняться со временем.

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

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

encoding) { case 3: return imap_base64($text); case 4: return imap_qprint($text); default: return $text; } } // multipart if ($structure->type == 1) { foreach ($structure->parts as $index => $subStruct) { $prefix = ""; if ($partNumber) { $prefix = $partNumber . "."; } $data = get_part($imap, $uid, $mimetype, $subStruct, $prefix . ($index + 1)); if ($data) { return $data; } } } } return false; } function get_mime_type($structure) { $primaryMimetype = array("TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO", "OTHER"); if ($structure->subtype) { return $primaryMimetype[(int)$structure->type] . "/" . $structure->subtype; } return "TEXT/PLAIN"; }

Функция GetBody() получает содержимое электронной почты, передавая свой ​​UID и соединение IMAP. Внутри функции мы взываем функцию get_part() с типом контента как text / HTML. Обычный текст сообщения электронной почты гораздо легче читать. Таким образом, мы сначала попытаться найти содержимое HTML внутри электронной почты.

Затем мы читаем структуру электронной почты с помощью функции imap_fetchstructure() . Мы изменили функции библиотеки, чтобы использовать UID, вместо того, чтобы, передавать FT_UID постоянно.

Тогда мы получим тип MIME сообщения электронной почты с помощью функции get_mime_type() . Есть восемь типов MIME, возвращаемые этой функцией в виде целых чисел:

  • 0 – TEXT
  • 1 – MULTIPART
  • 2 – MESSAGE
  • 3 – APPLICATION
  • 4 – AUDIO
  • 5 – IMAGE
  • 6 – VIDEO
  • 7 – OTHER

Мы превращаем возврат в реальный тип MIME строки с помощью массивов типов MIME.

Составные сообщения могут иметь большое число подтипов, так мы проходим рекурсивно через все подтипы, используя часть номеров и получение электронной почты с использованием imap_fetchBody() , когда правильный находится с использованием mime-type.

Затем, мы используем соответствующую функцию декодирования в соответствии с кодировкой типа сообщения и возврящаем содержание. Полный список доступных типов кодирования:

  • 0 – 7BIT
  • 1 – 8BIT
  • 2 – BINARY
  • 3 – BASE64
  • 4 – QUOTED-PRINTABLE
  • 5 – OTHER

Заключение

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

Одним из возможных применений imap функций является создание почтового демона, который будет управлять подпиской и отпиской пользователей от вашей почтовой рассылки. Для реализации этой задачи, обычно в рассылках используются два метода. Первый предполагает, что пользователь должен зайти на некую страницу и подтвердить свои действия, второй требует отправки письма. Второй так же требует, чтобы скрипт-обработчик регулярно запускался cron daemon?om. Из-за этого он не настолько популярен как первый способ.

Но, как можно заметить, наиболее серьезные рассылки используют второй способ. Поэтому, если у вас есть возможность использования crond, воспользуйтесь им.

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

Алгоритм работы самого скрипта придумать несложно. Демон устанавливает соединение с почтовым сервером, и проверяет наличие на нем писем. В случае если писем нет, работа скрипта прекращается.
Если письма есть, то происходит разбор заголовков первого письма. Просматривается поля from и subject. Если поле subject содержит один из двух допустимых вариантов заголовка (подписка или отписка), то запись, которой соответствует значение поля from либо становится активной (подтвержденной), либо удаляется из таблицы. В обоих случаях на адрес, указанный в поле from посылается соответствующее извещение о действиях скрипта. После этого письмо помечается для удаления. В случае если subject не содержит допустимых тем, посылается уведомление об ошибке, и письмо так же помечается для удаления. Затем скрипт переходит к следующему письму.
Закончив разбор всех писем, он очищает ящик.

Не буду утомлять читателя блок-схемами, так что сразу перейдем к делу. Для открытия ящика используется функция imap_open. Поскольку РНР поддерживает работу с несколькими протоколами, то необходимо явно указать, какой протокол используется для работы с ящиком. В нашем случае это POP3 на 110 порту (стандарт). Присваиваем результат выполнения скрипта переменной $my_box.


В дальнейшем вы увидите, что эта переменная будет использоваться пратически во всех imap функциях. Далее проверяем ящик на наличие писем. Проверку выполняет функция imap_num_msg.

$n = imap_num_msg ($my_box );

В результате переменная $n будет содержать количество писем в ящике. Число это может быть или больше нуля, или равно ему (если ящик пуст).
Если письма есть, то в цикле while выполняем разбор писем, последовательно увеличивая номер письма на единицу. Обратите внимание, что первое письмо в ящике будет иметь номер 0, как, и первый элемент массива.
Для увеличения номера письма, присваиваем переменной $m значение 0, а потом в условиях выполнения цикла увеличиваем ее на единицу $m++.

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

Imap_header возвращает в результате выполнения объект, содержащий исчерпывающую информацию о заголовке письма. Среди всего прочего, этот объект содержит массив from, в котором содержаться четыре значения. Это personal, adl, mailbox и host. Нас из них интересуют только mailbox и host. Подставляя их, мы получим адрес, с которого было отправлено письмо.


$h = $h -> from ;
a
foreach ($h as $k => $v ) {
$mailbox = $v -> mailbox ;
$host = $v -> host ;
$personal = $v -> personal ;
$email = $mailbox . ? @ ¬ . $host ;

Imap_fetch_overview - позволит нам узнать тему письма. Для этих же целей можно было бы использовать и imap_header но по ряду причин это, иногда может не сработать. Из массива, который возвращает эта функция, нам нужно только поле subject


foreach ($s as $k => $v ) {
$subj = $v -> subject ;
}

Дальнейшие наши действия сводятся к тому, чтобы вытащить email из базы, и в случае наличия его там, пометить всю строку с этой записью как «проверенную», либо удалить. Предположим, что после заполнения формы рассылки на сайте, подписчику присваивается статус 0, а после подтверждения подписки он меняется на 1.

if ($subj == "SUBSCRIBE" ) {
mysql_query ("UPDATE subscribe SET stat=1 WHERE email=$my_email" );

}
mysql_query ("DELETE FROM subscribe WHERE email = $my_email" );
$del = imap_delete ($my_box , $m );
}
else {
$del = imap_delete ($my_box , $m );
}

как уже говорилось выше, после выполнения всех действий скрипт очищает ящик.


Данная простейшая программа, лишь демонстрация того, что на РНР можно писать не только динамически изменяющиеся сайты, но и сервисы, которые пользователю вообще не видны. Конечно, по части написания скриптов для shell, рнр неприменим, в отличие от своего конкурента Perl, но тем не менее…

Листинг всей программы за исключением параметров соединения с базой (db.php):

include "db.php" ;
$my_box = imap_open ("{you.pop.host/pop3:110}" , "login" , "password" );
$n = imap_num_msg ($my_box );
$m = 0 ;
$add_text = "

Спасибо за подтверждение вашей подписки " ;
$add_sbj = "You added!" ;
$del_text = "

Извините но этот почтовый ящик используется
только для администрирования рассылки" ;
$err_sbj = "Error" ;
$headers = "From: Subscribe Robot

X-mailer: PHP4

Content-type: text/plain; charset=UTF-8
" ;
if($n != 0 ) {
while($m ++ < $n ) {
$h = imap_header ($my_box , $m );
$s = imap_fetch_overview ($my_box , $m );
$h = $h -> from ;
foreach ($h as $k => $v ) {
$mailbox = $v -> mailbox ;
$host = $v -> host ;
$personal = $v -> personal ;
$email = $mailbox . "@" . $host ;
$my_email = mysql_escape_string ($email );
}
foreach ($s as $k => $v ) {
$subj = $v -> subject ;
}
if ($subj == "SUBSCRIBE" ) {
mysql_query ("UPDATE table SET stat=1 WHERE email=$my_email" );
//print mysql_error();
$del = imap_delete ($my_box , $m );
mail ($email , $add_sbj , $add_text , $headers );
}
elseif ($subj == "UNSUBSCRIBE" ) {
mysql_query ("DELETE FROM table WHERE email = $my_email" );
$del = imap_delete ($my_box , $m );
mail ($email , $del_sbj , $del_text , $headers );
}
else {
$del = imap_delete ($open_box , $m );
mail ($email , $err_sbj , $err_text , $headers );
}
}
$clear = imap_expunge ($my_box );
}
?>

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

Теперь мне надо было быстренько разобраться как работать с протоколам IMAP , как получить письма из почтового сервера Yandex/Google.

Для более удобной работы я выбрал библиотеку PhpImap , так как она может быстро и легко реализует все нужные для меня задачи.

Подключение к почтовому серверу.

Теперь когда мы определились с выбором протокола и выбором библиотеки, будем пробовать подключатся к почтовому серверу.

Для полноценной работы PHP с протоколом IMAP, необходимо подключить расширение php_imap.dll/imap.so в файле php.ini.

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

//Подключаем библиотеки include("/phpImap/Mailbox.php"); include("/phpImap/IncomingMail.php"); //Для удобства создам константы для подключения к почтовому серверу. define("MAIL_IMAP_SERVER", "imap.yandex.ru"); define("MAIL_IMAP_SERVER_PORT", 993); define("MAIL_IMAP_LOGIN", " "); define("MAIL_IMAP_PASS", "example_pass"); define("MAIL_IMAP_PATH", "{".MAIL_IMAP_SERVER.":".MAIL_IMAP_SERVER_PORT."/imap/ssl}INBOX"); $mailbox = new PhpImap\Mailbox(MAIL_IMAP_PATH, MAIL_IMAP_LOGIN, MAIL_IMAP_PASS, __DIR__); try { $mailbox->getImapStream(); } catch (Exception $e) { die($e->getMessage()); }

Как мы видим конструктор класса Mailbox принимает следующие аргументы:

  • MAIL_IMAP_PATH - Cодержит в себе адрес сервера (MAIL_IMAP_SERVER), порт подключения (MAIL_IMAP_SERVER_PORT), тип соединения (imap) и показываем что соединение будет зашифровано (ssl). После фигурных скобок указываем папку к которой будем подключаться, в данном случае к входящим сообщениям (INBOX).
  • MAIL_IMAP_LOGIN - Почтовый ящик которому будем подключатся.
  • MAIL_IMAP_PASS - Пароль (чаще всего это пароль от почтового ящика).
  • __DIR__ - Это путь к папке в которой будут сохраняться вложенные файлы и почтовые сообщения.

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

Важно учесть во внимание то что в настройках Yandex почты у вас может быть отключена возможность подключения по протоколу IMAP.

Теперь давайте сравним подключение к почте Gmail.

Define("MAIL_IMAP_SERVER", "imap.gmail.com"); define("MAIL_IMAP_SERVER_PORT", 993); define("MAIL_IMAP_LOGIN", " "); define("MAIL_IMAP_PASS", "example_pass"); define("MAIL_IMAP_PATH", "{".MAIL_IMAP_SERVER.":".MAIL_IMAP_SERVER_PORT."/imap/ssl}INBOX");

Как мы видим оно практически не отличается от предыдущего подключения, но скорей всего у Вас сработает исключение при подключении к серверу.
Это проблема связана с тем что в Gmail работа протокола IMAP отключена по умолчанию . Включить её можно в настройках во вкладке Пересылка и POP/IMAP в опции Доступ по протоколу IMAP ⇒ Включить IMAP.

После того когда мы включили работу по протоколу IMAP нам надо создать пароль приложение . Для того чтобы его можно было создать нам необходимо сделать двух факторную авторизацию для данного профиля. После чего можно приступить к его созданию . Когда мы создадим новый пароль для приложения нам необходимо его будет вписать в константу MAIL_IMAP_PASS для подключения к серверу.

Учтите что при создании пароля приложений у Вас может быть так и не получится подключиться к серверу это связно с тем что данный пароль еще не применялся окончательно к сервису Gmail обычно это занимает 5-60 минут.

Выборка данных

После успешного подключения, мы можем выполнить запрос для получения потовых сообщений из сервера. Для этого мы будем использовать метод searchMailBox(string $criteria) который по сути является оберткой метода imap_search . Тут важно понять что аргумент $criteria является неким критерием поиска нужных нам сообщений, сам метод возвращает идентификаторы элементов которые в последствии нам пригодятся для получения детальной информации почтового сообщения.

$mailsIds = $mailbox->searchMailBox("ALL");

Как Вы уже догадались тут мы получаем все сообщения.
А теперь давайте попробуем разобраться и с другими не менее важными критериями поиска:

//Все сообщения за 3 дня. $mailsIds = $mailbox->searchMailBox("SINCE "".date("d-M-Y",strtotime("-3 day"))."""); //Непрочитанные сообщения за 3 дня. $mailsIds = $mailbox->searchMailBox("UNSEEN SINCE "".date("d-M-Y",strtotime("-3 day"))."""); //Поиск сообщений с таким соответствием в заголовке TEXT. $mailsIds = $mailbox->searchMailBox("TEXT "Новостная рассылка""); //Поиск сообщений с таким соответствием в заголовке BODY. $mailsIds = $mailbox->searchMailBox("BODY "Информационное сообщение""); //Поиск по емейлу отправителя. $mailsIds = $mailbox->searchMailBox("FROM " ""); //Получить сообщения по заголовку SUBJECT $mailsIds = $mailbox->searchMailBox("SUBJECT "Выпущены обновления для вашего телефона"");

Данный пример хорошо отражает основы использование критериев поиска.

Получение информации

Теперь когда у нас есть массив идентификаторов сообщений мы готовы его обработать:

//Получаем идентификатор последнего сообщения из массива. $id = end($mailsIds); //Получаем экземпляр объекта класса IncomingMail который содержит информацию о сообщении. $mail = $mailbox->getMail($id); //Получаем файлы вложенные к данному сообщению если он есть. $mail->getAttachments(); //Выводим сообщения. echo $mail->textHtml;

Вот мы и получили сообщения из нашего письма и его вложения без всяких заморочек.

Дополнительные возможности.

В данной библиотеке также присутствие ряд полезных методов для более удобной работы с почтовыми сообщениями:

Сохраняем сообщения по его ид.

$mailbox->saveMail($id,$id.".eml");

Устанавливаем сообщения как непрочитанное по его id.

$mailbox->markMailAsUnread($id);

Устанавливаем сообщения как прочитанное по его id.

$mailbox->markMailAsRead($id);

Устанавливаем на сообщение пометку по его id.

$mailbox->markMailAsImportant($id);

Удаляем сообщения по его id.

Одним из возможных применений imap функций является создание почтового демона, который будет управлять подпиской и отпиской пользователей от вашей почтовой рассылки. Для реализации этой задачи, обычно в рассылках используются два метода. Первый предполагает, что пользователь должен зайти на некую страницу и подтвердить свои действия, второй требует отправки письма. Второй так же требует, чтобы скрипт-обработчик регулярно запускался cron daemon-om. Из-за этого он не настолько популярен как первый способ.

Но, как можно заметить, наиболее серьезные рассылки используют второй способ. Поэтому, если у вас есть возможность использования cron, воспользуйтесь им.

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

Алгоритм работы самого скрипта придумать несложно. Демон устанавливает соединение с почтовым сервером, и проверяет наличие на нем писем. В случае если писем нет, работа скрипта прекращается. Если письма есть, то происходит разбор заголовков первого письма. Просматривается поля from и subject. Если поле subject содержит один из двух допустимых вариантов заголовка (подписка или отписка), то запись, которой соответствует значение поля from либо становится активной (подтвержденной), либо удаляется из таблицы. В обоих случаях на адрес, указанный в поле from посылается соответствующее извещение о действиях скрипта. После этого письмо помечается для удаления. В случае если subject не содержит допустимых тем, посылается уведомление об ошибке, и письмо так же помечается для удаления. Затем скрипт переходит к следующему письму. Закончив разбор всех писем, он очищает ящик.

Для открытия ящика используется функция imap_open . Поскольку PHP поддерживает работу с несколькими протоколами, то необходимо явно указать, какой протокол используется для работы с ящиком. В нашем случае это pop3 на 110 порту (стандарт). Присваиваем результат выполнения скрипта переменной $my_box.

$my_box = imap_open("{you.pop.host/pop3:110}", "login", "password");

В дальнейшем вы увидите, что эта переменная будет использоваться пратически во всех imap функциях. Далее проверяем ящик на наличие писем. Проверку выполняет функция imap_num_msg.

$n = imap_num_msg($my_box);

В результате переменная $n будет содержать количество писем в ящике. Число это может быть или больше нуля, или равно ему (если ящик пуст). Если письма есть, то в цикле while выполняем разбор писем, последовательно увеличивая номер письма на единицу. Обратите внимание, что первое письмо в ящике будет иметь номер 0, как, и первый элемент массива. Для увеличения номера письма, присваиваем переменной $m значение 0, а потом в условиях выполнения цикла увеличиваем ее на единицу $m++.

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

imap_header возвращает в результате выполнения объект, содержащий исчерпывающую информацию о заголовке письма. Среди всего прочего, этот объект содержит массив from, в котором содержаться четыре значения. Это personal, adl, mailbox и host. Нас из них интересуют только mailbox и host. Подставляя их, мы получим адрес, с которого было отправлено письмо.

$h = imap_header($my_box, $m); $h = $h->from; foreach ($h as $k => $v) { $mailbox = $v->mailbox; $host = $v->host; $personal = $v->personal; $email = $mailbox . "@" . $host;

imap_fetch_overview - позволит нам узнать тему письма. Для этих же целей можно было бы использовать и imap_header но по ряду причин это, иногда может не сработать. Из массива, который возвращает эта функция, нам нужно только поле subject

$s = imap_fetch_overview($my_box, $m); foreach ($s as $k => $v) $subj = $v->subject;

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

If ($subj == "subscribe") { mysql_query("update subscribe set stat=1 where email=$my_email"); $del = imap_delete($my_box, $m); mail($email, $add_sbj, $add_text, $headers); } else if ($subj == "unsubscribe") { mysql_query("delete from subscribe where email = $my_email"); $del = imap_delete($my_box, $m); mail($email, $del_sbj, $del_text, $headers); } else { $del = imap_delete($my_box, $m); mail($email, $err_sbj, $err_text, $headers); } после выполнения всех действий скрипт очищает ящик. $clear = imap_expunge($my_box);

Данная простейшая программа, лишь демонстрация того, что на PHP можно писать не только динамически изменяющиеся сайты, но и сервисы, которые пользователю вообще не видны.

Листинг всей программы за исключением параметров соединения с базой:

Include "config.php"; // соединение с БД $my_box = imap_open("{you.pop.host/pop3:110}", "login", "password"); $n = imap_num_msg($my_box); $m = 0; $add_text = " Спасибо за подтверждение вашей подписки "; $add_sbj = "you added!"; $del_text = " Вы были удалены из списка рассылки. "; $del_sbj = "delete from list"; $err_text = " Извините но этот почтовый ящик используется только для администрирования рассылки"; $err_sbj = "error"; $headers = "from: subscribe robot x-mailer: php4 content-type: text/plain; charset=windows-1251 "; if($n != 0) { while($m++ < $n) { $h = imap_header($my_box, $m); $s = imap_fetch_overview($my_box, $m); $h = $h->from; foreach ($h as $k =>$v) { $mailbox = $v->mailbox; $host = $v->host; $personal = $v->personal; $email = $mailbox . "@" . $host; $my_email = mysql_escape_string($email); } foreach ($s as $k =>$v) $subj = $v->subject; if ($subj == "subscribe") { mysql_query("update table set stat=1 where email=$my_email"); //print mysql_error(); $del = imap_delete($my_box, $m); mail($email, $add_sbj, $add_text, $headers); } else if ($subj == "unsubscribe") { mysql_query("delete from table where email = $my_email"); $del = imap_delete($my_box, $m); mail($email, $del_sbj, $del_text, $headers); } else { $del = imap_delete($open_box, $m); mail($email, $err_sbj, $err_text, $headers); } } $clear = imap_expunge($my_box); }

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


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

Сам я с этой задачей столкнулся достаточно давно, и тогда при наличии малого опыта работы c почтовыми программами Яндекса потратил массу времени и нервов для достижения требуемого результата. Первая моя ошибка заключалась в том, что, как многие веб-разработчики, я начел интенсивно искать похожие примеры в сети, но не воспользовался самой справкой (помощью) Яндекс. Да, там есть полезная информация, хотя её и очень мало, но она достаточно важная для написания такого рода скрипта (об этом будет ниже). На то время требовалось написать скрипт, суть которого сводилось: на Яндекс почту заказчика приходило письмо с прайсом товаров в xls формате раз в сутки, его необходимо было обработать (распарсить и сравнить с данными из БД интернет магазина и, в зависимости от результата, что-то обновить где-то, отключить или включить).

И первое, что мы сделаем перед написанием скрипта – это наметим наш план действий, который будет состоять из девяти пунктов:

  1. Настроим почту для получения доступа через почтовые протоколы;
  2. Наметим саму структуру PHP приложения и определимся с кодировкой файлов;
  3. Познакомимся с почтовым протоколом IMAP и его возможностями;
  4. Подключимся к Яндекс почте через логин и пароль аккаунта и отследим ошибки на этом этапе;
  5. Обработаем шапку письма;
  6. Получим и обработаем тела письма;
  7. Получим и сохраним вложенные файлы;
  8. Визуализируем проделанную работу;
  9. Сделаем выводы.

Тема довольно объёмная, но я постараюсь изложить всё максимально компактно и понятно. Пожалуй, приступим.

Настройка почты

Переходим в свою почту и заходим в настройки, как показано ниже на скриншоте:



Теперь мы попали в настройки работы почты через протоколы IMAP и POP3:


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

Структура приложения и её кодировка

В этом примере мы не будем придумывать сложную структуру приложения, так как она не нужна, а добавим только то, что необходимо (я работаю в редакторе Sublime Text):


  • tmp – папка в которую будем загружать вложенные файлы из письма, если они есть;
  • .htaccess – настройка серверной части, если у вас сервер apache;
  • functions.php – сюда будем добавлять наши функции;
  • main.css – файл стилей;
  • index.php – точка входа приложения;

Кодировку будем использовать UTF-8 и поэтому сразу заполним файл.htaccess следующими строками:

AddDefaultCharset utf-8 AddCharset utf-8 * CharsetSourceEnc utf-8 CharsetDefault utf-8

Протокол IMAP

Возвращаясь к первому пункту видно, что работать с почтой Яндекс можно также и через протокол POP3. Так почему же именно IMAP? Из двух этих протоков IMAP является более новым и альтернативным POP3, следовательно, у него есть ряд преимуществ (их можно изучить, воспользовавшись википедией), но в нашем случае на выбор повлияло только то, что он более новый. Лично я особой разницы не вижу, что использовать под конкретную задачу получения письма. Если по какой либо причине вам потребуется использовать протокол POP3 то все функции, которые применимы к IMAP будут работать и для него.

Подключаемся к Яндекс почте при помощи протокола IMAP

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


Теперь можно переходить непосредственно к самому коду:

Header("Content-Type: text/html; charset=utf-8"); error_reporting(0); require_once("functions.php"); $mail_login = "yandex_почта"; $mail_password = "пароль_от_почты"; $mail_imap = "{imap.yandex.ru:993/imap/ssl}"; // Список учитываемых типов файлов $mail_filetypes = array("MSWORD"); $connection = imap_open($mail_imap, $mail_login, $mail_password); if(!$connection){ echo("Ошибка соединения с почтой - ".$mail_login); exit; }else{ $msg_num = imap_num_msg($connection); $mails_data = array(); for($i = 1; $i <= $msg_num; $i++){ /* Работать с каждым письмом из IMAP-потока будем тут */ } } imap_close($connection);

Первым делом дополнительно указываем кодировку UTF-8 при помощи заголовка и отключаем отображение ошибок. Подключаем файл functions.php и указываем настройки, о которых выше была речь. В массиве $mail_filetypes прописываем форматы файлов, которые нам нужны. Так было решено сделать, чтобы отсеять ненужный мусор, и получать конкретные файлы. Соединение с почтой происходит при помощи функции imap_open(), которая при удачном выполнении возвращает IMAP-поток, а при неудачном - false (но если включить отображение ошибок, то это не так). Завершаем работу с потоками при помощи функции imap_close() передав ей индикатор соединения. Между этими двумя функциями идёт обычный условный оператор.

При удачном соединении при помощи imap_num_msg() узнаем число писем на почте и добавляем массив, в который будем помещать все нам необходимые данные из потока. Далее следует цикл, в котором будет обрабатываться каждое письмо по его номеру (нумерация происходит от 1) по отдельности.

Обработка шапки письма

Для получения шапки письма необходимо воспользоваться функцией imap_header(), вторым параметром которой являет номер письма:

// Шапка письма $msg_header = imap_header($connection, $i);

На данном этапе мы получим объект, из которого и будем вытягивать нужные нам данные, сохраняя в массив $mails_data. Вот пример одного из писем:

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

$mails_data[$i]["time"] = time($msg_header->MailDate); $mails_data[$i]["date"] = $msg_header->MailDate; foreach($msg_header->to as $data){ $mails_data[$i]["to"] = $data->mailbox."@".$data->host; } foreach($msg_header->from as $data){ $mails_data[$i]["from"] = $data->mailbox."@".$data->host; }

Сохраняем в нашем массиве: временную метку, дату получения письма, email получателя и отправителя и переходим к получению темы письма. Для этого нам необходимо вначале добавить три функции в файл functions.php:

Function check_utf8($charset){ if(strtolower($charset) != "utf-8"){ return false; } return true; } function convert_to_utf8($in_charset, $str){ return iconv(strtolower($in_charset), "utf-8", $str); } function get_imap_title($str){ $mime = imap_mime_header_decode($str); $title = ""; foreach($mime as $key => $m){ if(!check_utf8($m->charset)){ $title .= convert_to_utf8($m->charset, $m->text); }else{ $title .= $m->text; } } return $title; }

Названия говорящие и, я думаю, стоит пояснить только последнюю функцию. Она принимает закодированную строку и при помощи imap_mime_header_decode() декодирует ее, в результате чего возвращается массив объектов, у каждого из которых есть два свойства charset (кодировка) и text (текст темы). Дальше всё просто: в цикле проверяя кодировку, приводим к UTF-8 и склеиваем тему в единый заголовок и возвращаем его.

Теперь вернёмся в файл index.php и вытянем последний параметр:

$mails_data[$i]["title"] = get_imap_title($msg_header->subject);

На этом обработка шапки письма будет завершена.

Работаем с телом письма

Продолжаем постепенно формировать наш массив с обработанными данными письма и теперь для получения тела нам необходимо воспользоваться двумя функциями:

// Тело письма $msg_structure = imap_fetchstructure($connection, $i); $msg_body = imap_fetchbody($connection, $i, 1);

В первой переменной $msg_structure находится структура письма – это объект, в котором можно найти массу полезной информации, пример части этого объекта представлен ниже:

Что важно для решения нашей задачи:

  • type – первичный тип тела письма, в зависимости от того, что к нам приходит на почту он может меняться от 0 до 7 (каждой цифре советует свой вид контента который находиться в теле письма);
  • encoding – кодировка трансфера тела, меняется от 0 до 5 (0 - 7BIT, 1 - 8BIT, 2 – BINARY, 3 - BASE64, 4 - QUOTED-PRINTABLE, 5 - OTHER);
  • parts – массив частей письма, который соответствует структуре объекта уровнем выше.

Немного подробнее рассмотрим свойство parts. Первое, что нужно сказать это то, что в нулевой ячейке этого массива находиться информация, соответствующая именно тексту письма, а начиная с первого – вложенным файлам. Также в каждом объекте указывается type и в parameters кодировка в явном и в не явном виде.

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

Вторая функция imap_fetchbody() извлекает определённую часть письма, чаще всего в закодированном виде.

Теперь добавим переменную, в которую будем сохранять обработанную версию тела письма:

$body = "";

Вернёмся в файл functions.php и напишем рекурсивную функцию:

Function recursive_search($structure){ $encoding = ""; if($structure->subtype == "HTML" || $structure->type == 0){ if($structure->parameters->attribute == "charset"){ $charset = $structure->parameters->value; } return array("encoding" => $structure->encoding, "charset" => strtolower($charset), "subtype" => $structure->subtype); }else{ if(isset($structure->parts)){ return recursive_search($structure->parts); }else{ if($structure->parameters->attribute == "charset"){ $charset = $structure->parameters->value; } return array("encoding" => $structure->encoding, "charset" => strtolower($charset), "subtype" => $structure->subtype); } } }

Функция recursive_search() принимает один параметр – структуру письма, где последовательно проверяет свойства и достает три параметра: encoding, charset, subtype. Точкой выхода из рекурсии является отсутствие свойства parts с нулевой ячейкой. Больше пояснять тут особо нечего, из кода я думаю понятно, что и как происходит.

Добавим ещё одну функцию для переконвертации тела письма, которая нам потребуется в дальнейшем:

Function structure_encoding($encoding, $msg_body){ switch((int) $encoding){ case 4: $body = imap_qprint($msg_body); break; case 3: $body = imap_base64($msg_body); break; case 2: $body = imap_binary($msg_body); break; case 1: $body = imap_8bit($msg_body); break; case 0: $body = $msg_body; break; default: $body = ""; break; } return $body; }

$recursive_data = recursive_search($msg_structure); if($recursive_data["encoding"] == 0 || $recursive_data["encoding"] == 1){ $body = $msg_body; } if($recursive_data["encoding"] == 4){ $body = structure_encoding($recursive_data["encoding"], $msg_body); } if($recursive_data["encoding"] == 3){ $body = structure_encoding($recursive_data["encoding"], $msg_body); } if($recursive_data["encoding"] == 2){ $body = structure_encoding($recursive_data["encoding"], $msg_body); } if(!check_utf8($recursive_data["charset"])){ $body = convert_to_utf8($recursive_data["charset"], $msg_body); }

После того, как мы получили данные из рекурсии, постепенно проверяем кодировку трансфера и, в зависимости от этого, вызываем функцию structure_encoding() с соответствующими параметрами. В последнем условном операторе учитываем, то, что мы работает в UTF-8 и если после всех манипуляций у нас получится отличное от кодировки, перекодируем.

Осталось подвести черту:

$mails_data[$i]["body"] = base64_encode($body);

В теле письма может оказаться, как и обычный текст, так и HTML разметка со своими стилями. Кодируем в BASE64, чтобы при визуализации не поехала уже наша верстка.

Вложенные файлы

Вот, плавно подбираемся к концу написания нашего приложения:

// Вложенные файлы if(isset($msg_structure->parts)){ for($j = 1, $f = 2; $j < count($msg_structure->parts); $j++, $f++){ if(in_array($msg_structure->parts[$j]->subtype, $mail_filetypes)){ $mails_data[$i]["attachs"][$j]["type"] = $msg_structure->parts[$j]->subtype; $mails_data[$i]["attachs"][$j]["size"] = $msg_structure->parts[$j]->bytes; $mails_data[$i]["attachs"][$j]["name"] = get_imap_title($msg_structure->parts[$j]->parameters->value); $mails_data[$i]["attachs"][$j]["file"] = structure_encoding($msg_structure->parts[$j]->encoding, imap_fetchbody($connection, $i, $f)); file_put_contents("tmp/".iconv("utf-8", "cp1251", $mails_data[$i]["attachs"][$j]["name"]), $mails_data[$i]["attachs"][$j]["file"]); } } }

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

Хочу увидеть результат!

В процессе работы у нас сформировался массив с данными $mails_data, и для визуализации мы уже будем работать непосредственно с ним. В этой статье я использовал тестовое письмо, которое лежало у меня почте, давайте глянем, что у нас получилось в итоге:


Вот какого вида примерно должен у вас получиться массив, увы, пришлось скрыть содержание файла по личным причинам. Теперь переходим к нашей HTML разметке:

Яндекс Почта | <?php echo($mail_login);?>

Яндекс Почта (Входящие) |

Число писем:

писем нет
$mail):?>
Временная метка:
Дата:
Кому:
От:
Тема:
Письмо в base64:
Вложенные файлы:
$attach):?>
Тип:
Размер (в байтах):
Имя:
Тело:

Стили я не буду тут добавлять, так как они особой роли не играют, в итоге:


А на сервере в папке tmp у вас появится файл.

Заключение

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

Не получается получить письмо с вложением(
если отправляется почта с файлом - любым - то пропадает текст письма

Помогите пожалуйста

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