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

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

Почта в Laravel построена на Symfony Mailer, поэтому подключение к SMTP-релею — это шесть строк в .env. Ниже настройка fmailer и то, что нужно приложению дальше: классы Mailable, Markdown-шаблоны, вложения, отправка через очередь и тесты без реальной доставки.

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

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

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

Настройка .env

.envproperties
MAIL_MAILER=smtp
MAIL_HOST=smtp.fmailer.ru
MAIL_PORT=587
MAIL_ENCRYPTION=tls
MAIL_USERNAME=<логин токена>
MAIL_PASSWORD=<пароль токена>
MAIL_FROM_ADDRESS=noreply@mail.example.ru
MAIL_FROM_NAME="${APP_NAME}"

MAIL_ENCRYPTION=tls означает STARTTLS, а не шифрование с первого байта — это давняя путаница в названии. Для порта 465 нужно MAIL_ENCRYPTION=ssl. В Laravel 11 это заменили явной схемой:

properties
# Laravel 11 и новее понимают и схему целиком
MAIL_SCHEME=smtp        # "smtps" — шифрование сразу, порт 465
MAIL_HOST=smtp.fmailer.ru
MAIL_PORT=587
Laravel кэширует конфигурацию, поэтому изменённый .env ни на что не влияет, пока кэш не сброшен. Это самая частая причина, по которой правильные настройки почты «не работают».
bash
php artisan config:clear
php artisan queue:restart      # воркеры держат старый конфиг в памяти

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

Создайте Mailable с Markdown-шаблоном:

bash
php artisan make:mail OrderConfirmed --markdown=emails.orders.confirmed
app/Mail/OrderConfirmed.phpphp
<?php

namespace App\Mail;

use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

class OrderConfirmed extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(public Order $order) {}

    public function envelope(): Envelope
    {
        return new Envelope(subject: "Подтверждение заказа №{$this->order->id}");
    }

    public function content(): Content
    {
        return new Content(markdown: 'emails.orders.confirmed');
    }
}

И отправьте через фасад Mail:

php
use App\Mail\OrderConfirmed;
use Illuminate\Support\Facades\Mail;

Mail::to($order->customer->email)->send(new OrderConfirmed($order));
Адрес отправителя должен быть на подтверждённом домене, иначе подпись DKIM и запись SPF к нему не относятся. Письмо от @mail.ru через сторонний релей не проходит DMARC и попадает в спам.

Отправка через очередь

SMTP-доставка медленная относительно HTTP-ответа: соединение, TLS-рукопожатие, авторизация, само письмо. Класс выше использует трейт Queueable, поэтому вынести отправку из запроса — это замена одного слова:

php
// Отправку берёт на себя воркер — HTTP-ответ уходит сразу.
Mail::to($order->customer->email)->queue(new OrderConfirmed($order));

// Или с задержкой
Mail::to($order->customer->email)->later(now()->addMinutes(10), new OrderConfirmed($order));

Письмо из очереди ещё и повторяется автоматически, если SMTP-сервер ненадолго недоступен — при синхронной отправке это была бы ошибка 500.

Вложения

php
use Illuminate\Mail\Mailables\Attachment;

public function attachments(): array
{
    return [
        Attachment::fromPath(storage_path('invoices/2026-03.pdf'))
            ->as('invoice.pdf')
            ->withMime('application/pdf'),

        // или из данных, собранных в памяти
        Attachment::fromData(fn () => $this->pdf, 'invoice.pdf')
            ->withMime('application/pdf'),
    ];
}

Несколько получателей

Массив в Mail::to() кладёт все адреса в один заголовок To:, и получатели видят друг друга. Для писем клиентам — цикл:

php
// Отдельное письмо каждому — никто не видит чужих адресов.
foreach ($users as $user) {
    Mail::to($user->email)->queue(new WeeklyReport($user));
}
При использовании CC и BCC на каждого получателя внутри системы создаётся отдельное письмо, и лимиты расходуются соответственно.

Тесты без реальной отправки

properties
# .env.local — писать письма в storage/logs/laravel.log вместо отправки
MAIL_MAILER=log
php
use Illuminate\Support\Facades\Mail;
use App\Mail\OrderConfirmed;

Mail::fake();

$this->post('/orders', $payload);

Mail::assertQueued(OrderConfirmed::class, fn ($mail) => $mail->order->is($order));

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

ОшибкаПричина и решение
Настройки не применяются после правки .env Кэш конфигурации. php artisan config:clear и queue:restart.
Expected response code 250 but got code 535 Неверные MAIL_USERNAME / MAIL_PASSWORD.
Connection could not be established Порт заблокирован хостингом. Используйте 8587 или 8465.
stream_socket_enable_crypto(): SSL operation failedssl на порту 587 или tls на 465 — приведите в соответствие.
Код ответа 550 Домен в MAIL_FROM_ADDRESS не подтверждён.
Письма из очереди не уходят Не запущен воркер. Проверьте queue:work и таблицу failed_jobs.

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

Почему Laravel не видит новые настройки после правки .env?

Laravel кэширует конфигурацию. Выполните php artisan config:clear, а в продакшене — заново php artisan config:cache. Воркеры очередей тоже держат старый конфиг в памяти, их нужно перезапустить командой php artisan queue:restart.

Что писать в MAIL_ENCRYPTION — tls или ssl?

tls вместе с MAIL_PORT=587: несмотря на название, это STARTTLS. ssl вместе с MAIL_PORT=465 — шифрование с первого байта. В Laravel 11 и новее вместо этого можно задать MAIL_SCHEME со значением smtp или smtps и не указывать MAIL_ENCRYPTION вовсе.

Что означает ошибка «Expected response code 250 but got 535»?

535 — отказ авторизации: неверный MAIL_USERNAME или MAIL_PASSWORD. Нужен полный логин SMTP-токена домена и пароль, показанный при его создании. Если в пароле есть решётка или пробел, возьмите значение в кавычки прямо в .env.

Почему письма уходят локально, но на сервере таймаут?

Большинство хостингов блокируют исходящие порты 25, 465 и 587. Поставьте MAIL_PORT=8587 для STARTTLS или 8465 для шифрования с первого байта.

Как отправлять письма, не замедляя запрос?

Используйте Mail::queue() вместо Mail::send() и запустите воркер очереди. Mailable сериализуется в очередь и отправляется воркером, поэтому запрос не ждёт SMTP, а неудачная отправка повторяется вместо ошибки 500.

Как посмотреть письмо, не отправляя его?

Верните Mailable прямо из роута — Route::get('/preview', fn () => new OrderConfirmed($order)) — и Laravel отрисует его в браузере. Чтобы перехватывать реальные отправки в разработке, поставьте MAIL_MAILER=log: письмо целиком попадёт в storage/logs/laravel.log.

Что дальше