Перехват отправляемого email

Начинаем рубрику «для самых маленьких», с решением простеньких проблем.

Есть такая функция в PHP — mail(), и отправляет она, как все знают, электронную почту. Чуть меньше народу знает, что на самом деле отправляет не она, а mail-сервер, которому она просто передаёт письмо.

Ситуация с настоящим mail-сервером при тестировании сайта на локалке (под Windows) несколько неудобна по двум причинам. Во-первых, достаточно геморойно его установить. Во-вторых, при тестировании обычно не нужно действительно рассылать письма по настоящим адресам. Лучше всего в локальной версии все отправляемые письма складывать в отдельную папочку. Сразу видно сколько писем было отправлено, кому и что у них внутри.

Проще простого. На самом деле, mail() просто вызывает программу прописаную в php.ini в параметре sendmail_path и передаёт текст письма ей во входящий поток. Напишем свою программу и пропишем её здесь. Так как мы говорим о PHP, не будем ничего придумывать и напишем её на том же PHP.

Запуск сценария

Просто так PHP-сценарий запустить нельзя, нужно вызывать интерпретатор. В дистрибутиве PHP есть три исполняемых файла: php-cgi.exe, php.exe и php-win.exe. Нам нужен интерпретатор для работы с командной строкой — php.exe. Ему нужно указать флаг -f (выполнение кода из файла) и вторым аргументом передать путь к файлу. Таким образом прописываем в php.ini:

sendmail_path = "c:\web\php\php.exe -f c:\web\mail\sendmail.php"   ; ну или где у вас находится php

Перезапускаем сервер. Теперь при вызове mail() будет запускаться sendmail.php. Осталось написать его :)

sendmail.php

<?php
 
/* Получаем текст письма из входного потока */
$mail = '';
$fp = fopen('php://stdin', 'rb');
while (!feof($fp)) {
	$mail .= fread($fp, 512);
}
 
/* Корректировка заголовков */
$e = preg_split('~(\r\n\r\n)|(\n\r\n\r)|(\n\n)|(\r\r)~', $mail, 2);
$headersS = preg_split('~(\r\n)|(\n\r)|\n|\r~', $e[0]);
$headers  = Array();
$body     = isset($e[1]) ? $e[1] : '';
foreach ($headersS as $header) {
	$e = explode(':', $header, 2);
	$name  = strtolower(trim($e[0]));
	$value = trim(isset($e[1]) ? $e[1] : '');
	$headers[$name] = $value;
}
$headersS = implode("\r\n", $headersS);
if (!isset($headers['date'])) {
	$headersS = 'Date: '.gmdate('D, j M Y H:i:s')."\r\n".$headersS;
}
if (!isset($headers['content-type'])) {
	$headersS = "Content-Type: text/plain; charset=windows-1251\r\n".$headersS;
}
if (!isset($headers['mime-version'])) {
	$headersS = "MIME-Version: 1.0\r\n".$headersS;
}
$mail = $headersS."\r\n\r\n".$body;
 
 
/* Генерируем имя файла */
$fileNameBase = dirName(__FILE__).'/mails/'.date('Ymd_His');
$fileName     = $fileNameBase.'.eml';
$i = 1;
while (file_exists($fileName)) {
	$i++;
	$fileName = $fileNameBase.'_'.$i.'.eml';
}
 
/* Скидываем письмо в файл */
file_put_contents($fileName, $mail);
 
?>

Тестируем

<?php
 
$to      = 'to@mail.loc';
$from    = 'from@mail.loc';
$subject = 'Hello!';
$message = 'Hard porn on www.pornuha.loc!';
 
mail($to, $subject, $message, 'From: '.$from);
 
?>

Запускаем и получаем в папке mails (подкаталог каталога с sendmail.php) файл 20090305_134543.eml (отправлено 5 марта в 13:45):

MIME-Version: 1.0
Content-Type: text/plain; charset=windows-1251
Date: Thu, 5 Mar 2009 10:45:43
To: to@mail.loc
Subject: Hello!
From: from@mail.loc
 
Hard porn on www.pornuha.loc!

Кому мешают заголовки, можно открыть файл в любой почтовой программе (The Bat, например).

Пояснения к sendmail.php

Входной поток читается как обычный файл с именем php://stdin.

Так как одновременно может рассылаться несколько писем, имена файлов могут совпасть. Поэтому проверяем наличие файла и добавляем к нему префикс «_1», «_2″… пока не найдётся свободный.

Корректировка заголовков

Письмо состоит из служебных заголовков (например, To: to@mail.loc, в примере) и тела письма (собственно, отображаемый текст). Каждый заголовок располагается на отдельной строке, от тела письма отделяется пустой строкой.

В то же время переносы строк, в разных системах, как известно, кодируются по разному: \r\n, \n\r, \n или \r. Некоторые же почтовые программы (тот же Bat) под виндой глючат, если строки в заголовках переносятся не с помощью \r\n. А mail() норовит переносить строки одним \n.

Поэтому, для избежания проблем при просмотре письма, просто вытаскиваем заголовки, разделяем их, а потом собираем вместе с нужным переносом. Заодно проверяем наличие важных заголовков Mime-Version, Date и Content-Type, если их нет — вставляем значения по умолчанию.

Вот и всё ребятки…

10 комментариев »

  • Хм.. Как-то я о таком решение не подумал.

    На днях как раз ставил себе sendmail но толи из-за Windows Vista, то ли из-за моей криворукости(скорее 2-е конешн) ошибок никаких не выдается, но письма не рассылаются :)

    dallone, 5.03.2009, 14:45

  • dallone, Олежко мне еще при Москве этот скриптик давала :)

    adw0rd, 5.03.2009, 21:59

  • to dallone — у меня на висте та же история, XP нормуль

    Борис, 2.04.2009, 17:43

  • ОГРОМНОЕ СПАСИБО!!!!!!

    Vladimir, 11.04.2009, 12:15

  • Спасибо ща поробуем :)

    Историк, 15.04.2009, 18:23

  • Респект!

    sealpania, 2.05.2009, 11:52

  • Можно бы сделать пример более приятным, если добавить строки для передачи принятого письма в sendmail. Или теорию, о том как отправить письмо по назначению.

    Евгений, 3.07.2009, 11:39

  • Евгений, не понял вас.

    vasa_c, 3.07.2009, 12:07

  • Вопрос: а нахрена это надо? Чтобы получить копию письма в формате eml?

    Дмитрий, 29.06.2012, 9:56

  • Дмитрий, если хотите получить ответ, или сформулируйте вопрос без «нахрена», или перечитайте статью.

    vasa_c, 29.06.2012, 12:31

Leave a comment