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