Как использовать функцию mail() в PHP
28-03-2021Reading time ~ 5 min.PHP 5290
Для отправки почты в 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()
. Опять же, использовать по ситуации.