Сайт вебмастера

Как использовать функцию mail() в PHP

28-03-2021Время чтения ~ 5 мин.PHP 5444

Для отправки почты в PHP существует функция mail().

$to = 'user@site.com'; // кому
$subject = 'Hello, user!'; // тема письма
$message = 'How are you?'; // текст сообщения
$headers = ''; // дополнительные заголовки
 
mail($to, $subject, $message, $headers);

Сама по себе функция mail() ничего не отправляет, а лишь вызывает специальную программу (вроде sendmail, это указывается в php.ini), которой и передаёт входящие данные. А sendmail, в свою очередь уже и осуществляет физическую отправку письма.

За кажущейся простотой скрываются множество нюансов. Сложность в том, что обычное электронное письмо состоит из двух частей. Первая — это служебные заголовки, вторая — непосредственный текст сообщения. Исторически сложилось так, что мы не можем взять и просто отправить «голое» письмо, нужно ещё указать дополнительные заголовки. Если этого не сделать, то адресат не сможет его получить, поскольку почтовый сервер скорее всего заблокирует, посчитав спамом.

Другая проблема — кодировка. Для английского языка проблем нет, но если выйти за пределы 7 бит, то нужно дополнительно указывать способ кодирования и транспортировки. Сейчас ситуация ещё сложней, поскольку идёт повсеместное использование многобайтного UTF-8. Да и само письмо может быть обычным текстом, а может быть оформлено в HTML-коде.

Стандартная mail() для таких случаев использует аргумент $headers — дополнительные заголовки, поэтому перед отправкой почты, разработчику нужно сформировать строку этих заголовков.

Чтобы упростить работу с почтой, разработчики придумывают разные библиотеки, которые представляют собой оболочку над mail(). В качестве примера приведу ссылку на класс Email из CodeIgniter 4.

Такие мощные библиотеки оправданы, если требуется охватить весь объём задач: отправка вложений, SMTP протокол, пароли, криптография и т.д. Но в большинстве случаев хватает более простого варианта, где отправка осуществляется стандартными средствами сервера и типовыми mail-заголовками.

Какие задачи должна решать функция-обёртка для mail()?

Самая главная задача — это обеспечить корректное кодирование. Например есть кодирование Quoted-Printable, которое часто приводит к некоректному отображению кириллических символов. Раньше с кодированием был полный хаос, поскольку использовался и quoted-printable, и 8bit, и base64, что для разных сайтов часто приводил к разным проблемам отображения символов.

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

Но это ещё не всё. Если мы отправляем кириллический текст в UTF-8, то его нужно специальным образом обработать. Например:

$to = 'MaxSite.org <no-reply@maxsite.org>';

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

$to = 'Сайт MaxSite.org <no-reply@maxsite.org>';

Чтобы этого избежать, необходимо закодировать часть текста в base64 с добавлением обрамляющей сигнатуры:

$to = '=?UTF-8?B?' . base64_encode('Сайт MaxSite.org') . '?= <no-reply@maxsite.org>';

И такая ситуация со всеми заголовками письма и даже текстом письма.

Другая частая проблема — заголовок From. Он указывает на отправителя письма. Раньше его можно было не указывать, но несколько лет назад почтовики ужесточили свои правила и требуют, чтобы значение From указывало на адрес из домена сайта. Если этого не сделать, то письмо либо попадёт в спам, либо сразу удаляется.

Цель понятна — защита от спама.

Поле From указывает на адрес домена, например no-reply@maxsite.org. Но такой адрес фиктивный, нерабочий, а значит мы не сможем получить ответ на письмо. Вместо этого нужно ещё указывать заголовок Reply-To, который используется, когда получатель нажмёт кнопку «Ответить».

Какие ещё есть требования.

  • Нужно указывать MIME-Version.
  • Заголовки разделяются символами \r\n.
  • Текст может быть обычным (plain), либо html.

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

function mail64(string $to, string $subject, string $message, array $headers = [], array $additional = [])
{
    $CRLF = "\r\n";
 
    $subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
 
    if (stripos($message, 'HTML') === 0) {
        $message = substr($message, 4);
        $sendHeaders[] = 'Content-type: text/html; charset=UTF-8';
    } else {
        $sendHeaders[] = 'Content-type: text/plain; charset=UTF-8';
    }
 
    $message = str_replace("\r", '', $message);
    $message = str_replace("\n", $CRLF, $message) . $CRLF;
    $message = base64_encode($message);
 
    $sendHeaders[] = 'Content-Transfer-Encoding: base64';
    $sendHeaders[] = 'MIME-Version: 1.0';
 
    foreach ($headers as $key => $val) {
        $val = preg_replace_callback('~{{(.*?)}}~is', function ($m) {
            return '=?UTF-8?B?' . base64_encode(trim($m[1])) . '?=';
        }, $val);
 
        $sendHeaders[] = $key . ': ' . $val;
    }
 
    $sendHeaders = implode($CRLF, $sendHeaders);
 
    if ($additional) $sendHeaders .= $CRLF . implode($CRLF, $additional);
 
    return mail($to, $subject, $message, $sendHeaders);
}

Типовой пример использования:

$to = 'user@gmail.com'; // адрес получателя
 
$subject = 'Обратная связь'; // тема письма
 
// текст письма
$message = "HTML<b>Здравствуйте!</b>< br>< br>
Вы получили это письмо, поскольку отправили запрос бла-бла-бла< br>
бла-бла-бла";
 
// от кого - адрес сервера
$headers['From'] = '{{Сайт site.com}} <no-reply@site.com>'; 
 
// кому отвечать
$headers['Reply-To'] = '{{Сайт site.com}} <my-site@gmail.com>';
 
// отправка
// mail64($to, $subject, $message, $headers);
// либо, если нужно отловить ошибку:
$success = mail64($to, $subject, $message, $headers);
 
if (!$success)
    echo error_get_last()['message'];
else 
    echo 'Ok! Ваше письмо отправлено!';

Функция использует кодирование base64. Поля $subject и $message автоматом обрабатываются в эту кодировку. При этом, если текст сообщения начинается с фразы HTML, то он будет считаться html-разметкой и соответственно меняется Content-type. Если нужно отправить простой текст, то HTML в начале нужно удалить.

Что касается заголовков, то они представляют собой обычный массив, где ключ — это название заголовка. В значении можно использовать вхождение {{ текст }}, который автоматом будет обрамлён в base64.

Если нужно добавить вообще-вообще произвольные заголовки, то используется массив $additional. Лично я им никогда не пользовался, но возможность есть. Мало ли у кого какие задачи. :-)

Результат функции совпадает со стандартной mail(), то есть это true/false. Но, если нужно отловить ошибку отправки, то используется error_get_last(). Опять же, использовать по ситуации.

Похожие записи