Документация / PHP / PHPMailer

Отправка email в PHP через SMTP

Встроенная функция mail() — главная причина, по которой письма из PHP не доходят: она передаёт сообщение локальному sendmail, которого обычно нет, а если он есть — письмо уходит без авторизации и попадает в спам. Ниже — отправка через PHPMailer и fmailer, где подпись DKIM и запись SPF берёт на себя релей.

Логин и пароль можно получить на странице управления токенами домена.

Параметры подключения

ПараметрЗначение
SMTP-хостsmtp.fmailer.ru
Порт587
ШифрованиеSTARTTLS (соединение переводится в TLS до авторизации)
ЛогинЛогин SMTP-токена вашего домена
ПарольПароль этого токена — показывается один раз, при создании
Каждый режим доступен и на высоком порту: 8465 (SSL/TLS), 8587 (STARTTLS) и 8025 (без шифрования). Многие хостинги и облака блокируют исходящие 25, 465 и 587 — если соединение отваливается по таймауту, используйте соответствующий высокий порт

Установка

bash
composer require phpmailer/phpmailer

Первое письмо

Создавайте PHPMailer с аргументом true, чтобы ошибки бросали исключение, а не возвращали false: иначе сломанная конфигурация неотличима от рабочей:

send.phpphp
<?php

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

require 'vendor/autoload.php';

$mail = new PHPMailer(true);   // true = бросать исключения, а не возвращать false

try {
    $mail->isSMTP();
    $mail->Host       = 'smtp.fmailer.ru';
    $mail->Port       = 587;
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;   // 'tls'
    $mail->SMTPAuth   = true;
    $mail->Username   = getenv('SMTP_USER');
    $mail->Password   = getenv('SMTP_PASS');
    $mail->Timeout    = 10;
    $mail->CharSet    = PHPMailer::CHARSET_UTF8;          // иначе кириллица поедет

    $mail->setFrom('noreply@mail.example.ru', 'Магазин');
    $mail->addAddress('client@example.ru', 'Иван Иванов');

    $mail->isHTML(true);
    $mail->Subject = 'Подтверждение заказа №4417';
    $mail->Body    = '<h1>Заказ принят</h1><p>Доставка — завтра до 18:00.</p>';
    $mail->AltBody = 'Заказ принят. Доставка — завтра до 18:00.';   // задавайте всегда

    $mail->send();
} catch (Exception $e) {
    error_log('Письмо не отправлено: ' . $mail->ErrorInfo);
}

Для шифрования с первого байта на порту 465:

php
// Порт 465 — шифрование с первого байта
$mail->Port       = 465;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;   // 'ssl'
Блок SMTPOptions с verify_peer => false, который советуют на форумах, полностью выключает проверку TLS — соединение можно незаметно перехватить. Если проверка не проходит, обновите набор корневых сертификатов (openssl.cafile).

Вложения и картинки в теле письма

php
$mail->addAttachment('/srv/invoices/2026-03.pdf', 'invoice.pdf');

// Из строки, собранной в памяти
$mail->addStringAttachment($pdfData, 'invoice.pdf', 'base64', 'application/pdf');

// Картинка в теле письма — в HTML как <img src="cid:logo">
$mail->addEmbeddedImage('/var/www/assets/logo.png', 'logo', 'logo.png');

Пачка писем в одном соединении

По умолчанию PHPMailer подключается и авторизуется заново на каждое письмо. SMTPKeepAlive держит соединение открытым — только не забудьте очищать получателей, иначе каждое следующее письмо уйдёт ещё и всем предыдущим:

php
// Одно авторизованное соединение на всю пачку писем.
$mail->SMTPKeepAlive = true;

foreach ($users as $user) {
    $mail->clearAddresses();          // иначе получатели накапливаются
    $mail->addAddress($user->email);
    $mail->Subject = 'Еженедельный отчёт';
    $mail->Body    = renderReport($user);

    try {
        $mail->send();
    } catch (Exception $e) {
        error_log("Не отправлено на {$user->email}: {$mail->ErrorInfo}");
    }
}

$mail->smtpClose();
При использовании CC и BCC на каждого получателя внутри системы создаётся отдельное письмо, и лимиты расходуются соответственно.

Форма обратной связи

Никогда не подставляйте адрес посетителя в setFrom(): подпись DKIM и запись SPF вашего домена за чужой адрес не отвечают, письмо не проходит DMARC. Используйте addReplyTo():

php
// Форма обратной связи: From остаётся вашим, посетитель — в Reply-To.
$mail->setFrom('noreply@mail.example.ru', 'Сайт');
$mail->addAddress('sales@mail.example.ru');
$mail->addReplyTo($_POST['email'], $_POST['name']);

$mail->Subject = 'Заявка с сайта';
$mail->Body    = nl2br(htmlspecialchars($_POST['message'], ENT_QUOTES, 'UTF-8'));
$mail->AltBody = $_POST['message'];

Отладка подключения

php
// Временно вывести весь SMTP-диалог при отладке.
$mail->SMTPDebug   = SMTP::DEBUG_SERVER;
$mail->Debugoutput = 'error_log';

Разбор ошибок

ОшибкаПричина и решение
SMTP connect() failed Порт заблокирован — используйте 8587 или 8465 — либо шифрование не совпадает с портом.
SMTP Error: Could not authenticate Неверные доступы либо SMTPAuth остался false.
Ошибка SSL при подключенииENCRYPTION_SMTPS на 587 или ENCRYPTION_STARTTLS на 465.
Кириллица приходит «кракозябрами» Задайте $mail->CharSet = PHPMailer::CHARSET_UTF8.
Письмо уходит большему числу людей, чем нужно В цикле не вызывается clearAddresses().
SMTP Error: data not accepted (550)Домен в setFrom не подтверждён.

Частые вопросы

Почему функция PHP mail() не отправляет письма?

mail() передаёт письмо локальному бинарнику sendmail. На большинстве современных хостингов почтового сервера за ним нет, поэтому вызов возвращает true, а письмо исчезает. Даже там, где сервер есть, письмо уходит без авторизации, не проходит проверки SPF и DKIM у получателя и попадает в спам. SMTP-клиент вроде PHPMailer снимает обе проблемы.

Какую константу шифрования указывать в PHPMailer?

PHPMailer::ENCRYPTION_STARTTLS с портом 587 либо PHPMailer::ENCRYPTION_SMTPS с портом 465. Строковые значения 'tls' и 'ssl' означают то же самое. Несовпадение константы и порта — самая частая причина ошибки рукопожатия.

Можно ли отключить проверку сертификата через SMTPOptions?

Нет. Фрагмент с allow_self_signed и verify_peer => false, который кочует по форумам, полностью выключает проверку TLS-сертификата, и соединение становится доступным для перехвата. Настоящая причина обычно в устаревшем наборе корневых сертификатов — укажите актуальный в openssl.cafile.

Что выбрать — PHPMailer или Symfony Mailer?

Обе библиотеки поддерживаются. PHPMailer — меньшая зависимость, легко встраивается в старый код без фреймворка. У Symfony Mailer чище интерфейс, настройка одной строкой DSN и асинхронные транспорты, поэтому в современном приложении он удобнее — на нём построен и Laravel.

Что означает «SMTP connect() failed»?

Либо хостинг блокирует исходящие порты 25, 465 и 587 — тогда используйте 8587 или 8465, — либо тип шифрования не соответствует порту. Включите SMTPDebug = SMTP::DEBUG_SERVER, чтобы увидеть, на каком шаге обрывается диалог.

Обязательно ли задавать AltBody?

Да, всегда, когда Body содержит HTML. Письмо только с HTML-частью почтовые фильтры считают признаком спама, а в клиентах, предпочитающих простой текст, оно отобразится пустым. AltBody добавляет текстовую альтернативу и делает письмо multipart/alternative.

Что дальше