Отправка email-сообщений из PHP

Ответить
Аватара пользователя
Distructor
Администратор
Сообщения: 1607
Зарегистрирован: 28.12.2009
При написании своих скриптов, наверняка, практически у каждого возникало желание прикрутить к нему проверку e-mail, указываемого пользователем, либо отсылку на почту каких-либо уведомлений. В этой статье я постараюсь описать все подводные камни, с которыми я столкнулся при изучении данного вопроса.

Для начала предположим, что у вас есть установленный и настроенный почтовый сервер.
В этом случае достаточно использовать стандартную функцию php

Код: Выделить всё

bool mail ( string $to , string $subject , string $message [, string $additional_headers] )
Из описания получается, что достаточно указать email-назначения (to), тему письма (subject) и собственно сам текст письма (message), но на практике не все так просто.

Проблема 1
  • Более детально почитав описание функции можно найти такое ограничение
    Message. Each line should be separated with a LF (\n). Lines should not be larger than 70 characters.
    Т.е. текст сообщения должен быть разбит на строки символом "\n", причем длина строки не должна превышать 70 символов.
Проблема 2
  • Вторая проблема заключается в том, что в тексте письма строка не должна начинаться с точки. Т.к. она в начале строки используется для обозначения конца тела письма. На php.net предлагают заменять такие точки на '..'

    Код: Выделить всё

    $text = str_replace("\n.", "\n..", $text);
Проблема 3
  • И наконец проблема кодировки. Если ее не указать, то программы для работы с почтой будут подставлять свою кодировку по умолчанию и не факт, что она совпадет с той в которой написано ваше письмо.
Теперь самое время перейти к функции, добавляющей все необходимые заголовки для передачи письма в кодировке UTF-8.

Код: Выделить всё

    function send_email ($to,$subject,$message) {
        $from_email = 'notexist@test.ru';  // Адрес отправителя
        $from_name = 'Вася Пупкин'; // Имя отправителя

        $headers  = 'MIME-Version: 1.0' . "\r\n";
        $headers .= 'Content-type: text/html; charset=UTF-8; '."\r\n";
        $headers .= 'From: =?utf-8?b?' . base64_encode($from_name) . "?= <$from_email>\r\n";
        $message = str_replace("\n.", "\n..", $message);
        mail ( $to, '=?utf-8?b?'.base64_encode($subject).'?=', $message, $headers );
    } 
Если же почтовый сервер не установлен, то можно воспользоваться кодом приводимым ниже, который позволит использовать любой ваш почтовый ящик для отправки писем через него.

Код: Выделить всё

    function send_mail($to,$subject,$message)
    {
        $address   = 'smtp.mail.ru'; // адрес smtp-сервера
        $port      = 25;          // порт (стандартный smtp - 25)
        $login     = 'notexist';    // логин к ящику
        $pwd       = 'pass';    // пароль к ящику
        $from      = 'notexist@mail.ru';  // адрес отправителя
        $from_name = 'Вася Пупкин'; // Имя отправителя
        
                        $message = str_replace("\n.", "\n..", $message);
        try {
        $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if ($socket < 0)
            throw new Exception('socket_create() failed: '.socket_strerror(socket_last_error())."\n");
        $result = socket_connect($socket, $address, $port);
        if ($result === false)
            throw new Exception('socket_connect() failed: '.socket_strerror(socket_last_error())."\n");
        // Читаем информацию о сервере
        read_smtp_answer($socket);
        // Приветствуем сервер
        write_smtp_response($socket, 'EHLO '.$login, true);
        // Делаем запрос авторизации
        write_smtp_response($socket, 'AUTH LOGIN', true);
        // Отравляем логин
        write_smtp_response($socket, base64_encode($login), true);
        // Отравляем пароль
        write_smtp_response($socket, base64_encode($pwd), true);
        // Задаем адрес отправителя
        write_smtp_response($socket, 'MAIL FROM: <'.$from.'>', true);
        // Задаем адрес получателя
        write_smtp_response($socket, 'RCPT TO: <'.$to.'>', true);
        // Готовим сервер к приему данных
        write_smtp_response($socket, 'DATA', true);
        // Отправляем данные
        $header  = 'MIME-Version: 1.0' . "\r\n";
        $header .= 'Content-type: text/html; charset=UTF-8; '."\r\n";
        $header .= "To: $to\r\n";
        $header .= 'From: =?utf-8?b?' . base64_encode($from_name) . "?= <$from>\r\n";
        $header .= "Subject: =?utf-8?b?".base64_encode($subject)."?=\r\n";
        write_smtp_response($socket, $header.$message."\r\n.", true);
        // Отсоединяемся от сервера
        write_smtp_response($socket, 'QUIT', true);
        return true;  
        } catch (Exception $e) {
        echo "\nError: ".$e->getMessage();
        }
        if (isset($socket))
        socket_close($socket);
    }
   
    // Функция для чтения ответа сервера. Выбрасывает исключение в случае ошибки
    function read_smtp_answer($socket) {
        $read = socket_read($socket, 1024);
        if ($read{0} != '2' && $read{0} != '3') {
            if (!empty($read)) {
                throw new Exception('SMTP failed: '.$read."\n");
            } else {
                throw new Exception('Unknown error'."\n");
            }
        }
    }
   
    // Функция для отправки запроса серверу
    function write_smtp_response($socket, $msg, $read=false) {
        $msg = $msg."\r\n";
        socket_write($socket, $msg, strlen($msg));
        if ($read)
          read_smtp_answer($socket);
    }
Несколько уточнений
  • - т.к. мы указали тип содержимого как 'Content-type: text/html' мы можем в теле письма использовать html теги
    - из за возможности использования тегов, я не стал писать простой автоматический разбиватель текста сообщения на строки на 70 символов, т.к. получится что некоторые теги будут порублены на части. Так что вам надо либо следить самим за подготовкой текста для отправки, либо написать продвинутый парсер/разбиватель, который будет следить за тегами и непозволять их резать.
    - в примерах выше формируется письмо в кодировке UTF-8 и ожидается, что заголовок и текст передаваемый функциям тоже в кодировке UTF-8. Если же исходный текст, например, в cp1251, то необходимо его перекодировать

    Код: Выделить всё

    $subject = mb_convert_encoding($subject, "UTF-8", "cp1251");
Copyright © 2009 Creaternal (v-tanke.ru).
При копировании информации, ссылка на источник и указание автора обязательны.

Аватара пользователя
Distructor
Администратор
Сообщения: 1607
Зарегистрирован: 28.12.2009
Как выяснилось возможны проблемы с "\r\n" (Thunderbird понимает их как 2 переноса строки и, т.к. 2 переноса строки сигнализируют о завершении блока заголовков, то кидает все наши дополнительные заголовки в тело письма), так что лучше использовать вместо них только "\n".

Так же может пригодиться указание адреса для ответа:

Код: Выделить всё

$headers .= 'Reply-To: =?utf-8?b?' . base64_encode($username) . "?= <$useremail>\n";

Аватара пользователя
Distructor
Администратор
Сообщения: 1607
Зарегистрирован: 28.12.2009
Отправление письма с вложением, для случая если установлен почтовый сервер

Код: Выделить всё

    function send_email ($to, $subject, $message, $filecontent, $filename) {
        $from_email = 'notexist@test.ru';  // Адрес отправителя
        $from_name  = 'Вася Пупкин';           // Имя отправителя
        $message = str_replace("\n.", "\n..", $message);

        $boundary = "--".md5(uniqid(time())); // генерируем разделитель 
        
        $multipart .= "--$boundary\r\n"; 
        $multipart .= "Content-Type: text/html; charset=UTF-8; \r\n"; 
        $multipart .= "Content-Transfer-Encoding: Quot-Printed\r\n\r\n"; 
        $multipart .= "$message\n\n"; 

        $message_part = "--$boundary\n"; 
        $message_part .= "Content-Type: application/octet-stream\r\n"; 
        $message_part .= "Content-Transfer-Encoding: base64\r\n"; 
        $message_part .= "Content-Disposition: attachment; filename = \"".$filename."\"\r\n\r\n"; 
        $message_part .= chunk_split(base64_encode($filecontent))."\r\n"; 
        
        $multipart .= $message_part."--$boundary--\r\n"; 
        
        $headers  = 'MIME-Version: 1.0' . "\r\n";
        $headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n"; 
        $headers .= 'From: =?utf-8?b?' . base64_encode($from_name) . "?= <$from_email>\r\n";
        mail ( $to, '=?utf-8?b?'.base64_encode($subject).'?=', $multipart, $headers );
    } 

Ответить