Php mail base64 encoding

Как грамотно отправлять почту из скриптов (в частности — на PHP)

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

Для того, чтобы быть уверенным, что ваше сообщение отправляется действительно верно, необходимо иметь по меньшей мере базовые представления о формате почтового сообщения. Формат почтового сообщения описан в нескольких стандартизирующих документах, основными из которых являются RFC 822 (описывает формат передачи простого текста на английском языке) и RFC 2045 и далее (описывает расширения этого формата для передачи произвольных данных).

Формат почтового сообщения

Ниже приведен самый простой пример текстового сообщения, составленного в соответствии с приведенными выше стандартами и готового к отправке.

From: =?windows-1251?b?0J7RgtC/0YDQsNCy0LjRgtC10LvRjD89?=
To: =?windows-1251?b?0J/QvtC70YPRh9Cw0YLQtdC70Yw/PQ==?=
Subject: =?windows-1251?b?0Y3RgtC+INGC0LXQvNCwINGB0L7QvtCx0YnQtdC90LjRjz89?=
Content-Type: text/plain; charset=«windows-1251»
Content-Transfer-Encoding: 8bit

Это почтовое сообщение на русском языке
Содержит несколько строк

Именно в таком формате клиент для отправки почты (MS Outlook или Mozilla Thunderbird) подготавливает сообщение, а затем отправляет его получателю (кстати, большинство почтовых клиентов позволяют просмотреть исходный код сообщения, в Mozilla Thunderbird, например, для этого служит комбинация клавиш Ctrl+U). Задача нашего скрипта языке PHP — добиться точно такого же формата письма.

Как видно из приведенного выше примера, электронное письмо содержит две части: в одной (верхней) размещаются заголовки, а в другой (нижней) собствено текст письма. Отделены эти части друг от друга пустой строкой. Заголовки состоят из строк, в которых содержится тема письма (Subject), имя и адрес отправителя (From), получателя (To) и другая информация. В самом простом случае каждая строка содержит пару «ИмяЗаголовка: ЗначениеЗаголовка». Особенно необходимо подчеркнуть, что, согласно стандартам, в заголовках ни при каких обстоятельствах не должны содержаться символы, не присутствующие в ASCII таблице — латинские буквы, цифры, знаки пунктуации и псевдографики.

Грамотное использование русских символов в заголовках почтового сообщения

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

Кодировка может быть любой из списка «windows-1251», «koi8-r», «utf-8» и т.д. Во всех случаях, как правило, кодировка сообщения будет совпадать с кодировкой в которой работает сайт. То есть в большинстве случаев это будет «windows-1251», реже — «utf-8».

Способ кодирования указывает на то, каким именно образом русские символы будут преобразованы в безопасный набор. Способа определяется два: так называемый «Q-encoding» (обозначается одной буквой «Q») и «Base64» (обозначается одной буквой «B»).

Читайте также:  Javascript elem in array

К сожалению, штатной функции, которая бы могла бы обычную строку преобразовать в Q-encoded текст, в PHP нет, зато есть функция, которая умеет выполнять аналогичное преобразование в Base64. Итак, PHP код правильного создания заголовка темы почтового сообщения может выглядеть следующим образом:

$subject = «=?windows-1251?b?». base64_encode($_POST[«subject»]). «? =?windows-1251?B?». base64_encode($_POST[«username»]). «?= «;

Content-type: multipart/.

С этим заголовком знаком любой разработчик, которому доводилось решать проблемы отправки писем с вложениями или HTML письмами. И зачастую письма, сформированные без использования библиотек вроде PEAR::Mail_mime отображаются не очень корректно. Практика показывает, что если при формировании письма жестко придерживаться стандарта, которы задается в RFC (в частности — RFC 2046) — подавляющее большинство клиентских программ (включая таких любителей придерживаться стандартов, как Mozilla Thunderbird) отображает письмо корректно. Далее мы будем исходить из того, что читатель этого документа представляет себе основной синтаксис команд и понимает, что таке boundary и почему необходимо указывать Content-type для каждой из частей письма. Постараемся отметить основные ошибки.

Ошибка первая — неверный subtype

Тип multipart имеет три субтипа — mixed, alternative и related, которые используются синтаксически одинаково, но имеют разное предназначение

    mixed — используется, когда в рамках одного почтового сообщения имеется несколько независимых друг от друга, и равнозначных частей. Самый простой пример такого письма — сообщение с вложением.
    alternative — используется, когда в одном почтовом сообщении содержится несколько частей, содержащих одну и ту же информацию, предназначенную для отображения на различном клиентском ПО — например текстовая и HTML версия одного и того же письма.
    related — используется, когда в одном почтовом сообщении содержится несколько частей, формирующих один итоговый документ. Яркий пример — HTML письмо с картинками. Запомните, по стандарту только в этом случае должны работать ссылки на Contend-id элементов (вида ).
    Помните и применяйте по назначению.

Ошибка вторая — неверный порядок частей

    mixed — порядок частей для наших задач не имеет значения.
    alternative — части должны быть расставлены по порядку, от более простых к более сложным. RFC регламентирует процесс выбора одной из версий письма клиентом пользователя примерно так: «В общем случае, почтовый клиент должен отображать последнюю доступную ему версию документа». Т.е. при формировании текстовой и HTML-версий письма необходимо вперед поставить текстовую.
    related — первой в очереди должна идти основная часть (HTML документ, например). Следом — все остальные. По большому счету, стандартом регламентирован специальный параметр «start», который указывает на основную часть документа, но этим лучше не злоупотреблять.

Ошибка третья — выбор только одного субтипа

Зачастую разработчик, формирующий из программы письмо забывает, что любая из частей письма может так же иметь Content-type: multipart, а значит можно выстроить некоторое подобие древовидной структуры, гарантирующей, что каждая из частей письма займет правильное место. Вот как примерно может выглядеть структура письма, имеющего текстовую и HTML версию (HTML с картинками), а так же приложенный документ MS Word:

  • Content-type: multipart/mixed
    • Content-type: multipart/alternative
      • Content-type: text/plain
        Content-type: multipart/related
        • Content-type: text/html
          Content-type: image/jpeg
          Content-type: image/jpeg

        И напоследок — еще пара рекомендаций

        • Всегда делайте text/plain вариант письма — никто не может предсказать, как именно будут читать Ваше письмо.
          Не ленитесь и придерживайтесь стандартов.
          Если интересно — http://people.dsv.su.se/~jpalme/ietf/mhtml-test/mhtml.html тут есть несколько примеров.

        Источник

        How to make the PHP mail() function awesome

        In my special case, emails that have been sent often contained German Umlauts like ö, ä, ü, and ß. If you send an email with mail() containing one of those special characters, you will get ugly results like this:

        PHP mail wrong content encoding

        If you look into the email headers, you will notice that there is either any information about the Content-Type nor the Content-Transfer-Encoding , so it shouldn’t be surprising seeing wrong displayed characters in the email.

        PHP mail wrong encoding for subject

        Notice how broken the subject line looks like in the email source code. I’m pretty sure a lot of email clients will have problems to display the email subject correctly.

        What can we do to solve the encoding problems?

        Additional headers – let’s make PHP mail() awesome!

        For our rescue, the mail() function comes with a fourth parameter, named $additional_headers . This parameter allows us to define custom email headers and pass them through as string (or as an associative array, since PHP 7.2.0) to the function.

        This means we can define our own headers for the email and fix practically the most known drawbacks of the PHP’s mail() function.

        Let’s get into coding, I can’t hold my hands from the awesome Atom Editor any longer, and extend the mail() function for UTF-8 support. We also will be using Base64 encoding for the email text body to ensure a robust character encoding. And we will also encode the subject line and some other header parts.

        // Force PHP to use the UTF-8 charset header('Content-Type: text/html; charset=utf-8'); // Define and Base64 encode the subject line $subject_text = 'Test email with German Umlauts öäüß'; $subject = '=?UTF-8?B?' . base64_encode($subject_text) . '?='; // Add custom headers $headers = 'Content-Type: text/plain; charset=utf-8' . "\r\n"; $headers .= 'Content-Transfer-Encoding: base64'; // Define and Base64 the email body text $message = base64_encode('This email contains German Umlauts öäüß.'); // Send mail with custom headers if (mail('recipient@domain.com', $subject, $message, $headers))  echo 'Email has been sent!'; > else  echo 'Oops, something gone wrong!'; > 

        Let’s send a test email and take a look into the email headers:

        PHP mail encoding fixed

        As you can see, the subject line is encoded with the combination of UTF-8 and Base64. This ensures that the email subject will be displayed on most devices and clients correctly.

        The PHP’s mail() function has properly applied our custom headers for Content-Type and Content-Transfer-Encoding , which is really great so far. Now the email body text is encoded with the Base64 algorithm and we can use all characters supported by UTF-8 inside the email body.

        One important task is finished. We have fixed the encoding part.

        Next, we can go further and add some more headers to get full control over the email’s recipient and sender information. If you like you can even use the Reply-To header to define freely the contact information for email replies.

         . // Define the sender $sender = 'sender@domain.com'; // or: // $sender = '=?UTF-8?B?' . base64_encode('John Döe') . '?= '; // Define the recipient $recipient = 'recipient@domain.com'; // or: // $recipient = '=?UTF-8?B?' . base64_encode('Margret Müller') . '?= '; // Add custom headers $headers = 'Content-Type: text/plain; charset=utf-8' . "\r\n"; $headers .= 'Content-Transfer-Encoding: base64' . "\r\n"; $headers .= 'From: ' . $sender; . mail($recipient, $subject, $message, $headers); 

        Important: You must always pass the recipient value as the first parameter to the mail() function!

        As soon as you check the email headers again you will notice that the new headers have been applied to the email too. If you define custom contact information make sure you use the right string format for the pairs name and email address.

        Please notice in the code example above that we have encoded the sender’s name with Base64. This needs to be done only for the name but not for the email address.

        You have to agree with me that we have extended the mail() function in the right direction. The last thing one can wish is HTML support. This part is very easy to do, just modify the header corresponding for the Content-Type :

        $headers = 'Content-Type: text/html; charset=utf-8' . "\r\n"; 

        You don’t need to do anything else.

        As email body you can now use text and HTML, which, by the way, will be Base64 encoded. The result is a modern-looking email containing all sorts of fancy HTML formatting.

        For best practice and most email client support, I would recommend using inline CSS styles instead of defining them in the section of the email wrapped inside the block.

        Conclusion

        I appreciate that the PHP developers gave us the mail() function and enabled us to extend it with custom headers. By using the $additional_headers parameter of the mail() function we got great power over the email headers.

        But, with great power comes great responsibility, as we all know! Modify the headers only if you really need to. In the worst case, you can break the whole mailing functionality of your app – you don’t want it to happen.

        Workarounds and examples used in this article I use in a slightly modified version on my website NewsletterMarketer and some of my other websites for the contact form, transaction emails and to send bulk email newsletters.

        I also maintain a Github repository for a custom PHP function, that is basically a wrapper for the mail() function and has already implemented all fixes shown in this article and some additional great features.

        For all small email tasks like contact forms and all kinds of transaction emails I always use my customized mail() function. But, if I need to use external mail servers and send emails through SMTP I mostly use the mature, production-ready PHP class called PHPMailer.

        I hope you can now enjoy the PHP’s mail() function as I do 🙂

        Источник

Оцените статью