Wysyłka maili przez SMTP to jeden z tych tematów, które wyglądają prosto — dopóki coś nie działa na produkcji o 23:00. Jeśli konfigurujesz SMTP w Symfony lub szukasz właściwego formatu Mailer DSN w Laravel, ten artykuł przeprowadzi Cię przez cały proces: od pliku .env, przez konfigurację DSN, po diagnostykę najczęstszych błędów. Bez zbędnego teoryzowania — tylko konkretne kroki i przykłady.
Jak działa wysyłka maili w PHP frameworku?
Zarówno Symfony, jak i Laravel korzystają z abstrakcji nad protokołem SMTP, która ukrywa niskopoziomowe szczegóły połączenia. W Symfony jest to komponent Symfony Mailer (od wersji 4.3, zastąpił SwiftMailer), a w Laravel — fasada Mail oparta na bibliotece Symfony Mailer (od Laravel 9). Oba frameworki odczytują dane połączenia ze zmiennych środowiskowych, co ułatwia zarządzanie konfiguracją między środowiskami (dev, staging, produkcja).
Kluczowy koncept wspólny dla obu to DSN (Data Source Name) — jednoliniowy ciąg znaków opisujący protokół, dane uwierzytelniające, host, port i opcje. Zamiast rozbijać konfigurację na pięć osobnych zmiennych, cały zestaw parametrów trafia do jednej zmiennej, np. MAILER_DSN.
Konfiguracja SMTP w Symfony
Format DSN w Symfony Mailer
Symfony Mailer obsługuje kilka transportów: SMTP, sendmail, Amazon SES, Mailgun i inne. Dla własnego serwera SMTP format DSN wygląda następująco:
smtp://użytkownik:hasło@host:port
Przykładowe wartości dla popularnych konfiguracji:
| Scenariusz | DSN |
|---|---|
| Własny serwer SMTP, port 587 (STARTTLS) | smtp://login%40domena.pl:haslo@mail.domena.pl:587 |
| SMTP z SSL, port 465 | smtps://login%40domena.pl:haslo@mail.domena.pl:465 |
| Lokalny serwer bez auth (dev) | smtp://localhost:1025 |
| Wyłączona wysyłka (dev/test) | null://null |
Uwaga: jeśli login zawiera znak @ (np. adres e-mail), musisz go zakodować jako %40. Niezakodowany małpa-a jest jednym z najczęstszych powodów błędu parsowania DSN.
Plik .env i mailer.yaml
W pliku .env (lub .env.local dla lokalnych nadpisań) ustaw:
MAILER_DSN=smtp://login%40firma.pl:TajneHaslo123@smtp.firma.pl:587
Następnie w config/packages/mailer.yaml upewnij się, że transport wskazuje na tę zmienną:
framework:
mailer:
dsn: '%env(MAILER_DSN)%'
Od Symfony 5.4 możesz też konfigurować wiele transportów (np. podstawowy i zapasowy) za pomocą klucza transports i polityki failover — przydatne, gdy zależy Ci na niezawodności wysyłki.
Wysyłka testowa z konsoli
Symfony nie ma wbudowanego polecenia do testowej wysyłki, ale możesz szybko zweryfikować konfigurację, tworząc prosty command lub używając narzędzia debug:config framework mailer, które wyświetli aktywną konfigurację mailera (bez ujawniania hasła w plain-text).
Konfiguracja SMTP w Laravel
Zmienne środowiskowe SMTP w Laravel
Laravel tradycyjnie używa osobnych zmiennych zamiast jednego DSN, choć od wersji 9 obsługuje też format DSN przez zmienną MAIL_URL. Klasyczny zestaw w pliku .env:
MAIL_MAILER=smtp
MAIL_HOST=smtp.firma.pl
MAIL_PORT=587
MAIL_USERNAME=login@firma.pl
MAIL_PASSWORD=TajneHaslo123
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@firma.pl
MAIL_FROM_NAME="Moja Firma"
Wartość MAIL_ENCRYPTION przyjmuje tls (STARTTLS, port 587) lub ssl (port 465). Wpisanie null wyłącza szyfrowanie — nie rób tego na produkcji.
Format Mailer DSN w Laravel (od wersji 9)
Alternatywnie możesz użyć jednej zmiennej MAIL_URL, która ma pierwszeństwo przed pozostałymi zmiennymi SMTP:
MAIL_URL=smtp://login%40firma.pl:TajneHaslo123@smtp.firma.pl:587?encryption=tls
Format jest identyczny z Symfony Mailer, bo Laravel 9+ używa go pod spodem. To wygodne, gdy zarządzasz konfiguracją przez zewnętrzne narzędzia (Vault, AWS Secrets Manager) i chcesz przechowywać jeden sekret zamiast pięciu.
Plik config/mail.php
Plik config/mail.php odczytuje zmienne środowiskowe i nie powinien zawierać hardkodowanych danych. Upewnij się, że sekcja mailers.smtp wygląda mniej więcej tak:
'smtp' => [
'transport' => 'smtp',
'host' => env('MAIL_HOST', 'localhost'),
'port' => env('MAIL_PORT', 587),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,
],
Po zmianie zmiennych środowiskowych na produkcji pamiętaj o php artisan config:clear — Laravel agresywnie cachuje konfigurację i bez tego polecenia zmiany w .env nie zostaną uwzględnione.
Testowa wysyłka z Artisan
Laravel oferuje wygodne polecenie do weryfikacji konfiguracji mailera:
php artisan mail:send --to=test@example.com App\\Mail\\TestMail
Jeśli nie masz gotowej klasy Mailable, możesz użyć pakietu laravel/tinker i wywołać Mail::raw('Test', fn($m) => $m->to('test@example.com')) bezpośrednio z REPL-a.
Porównanie podejść: Symfony vs Laravel
| Cecha | Symfony Mailer | Laravel Mail |
|---|---|---|
| Zmienna DSN | MAILER_DSN |
MAIL_URL (lub osobne zmienne) |
| Plik konfiguracyjny | config/packages/mailer.yaml |
config/mail.php |
| Domyślny transport | SMTP (konfigurowalne) | SMTP (konfigurowalne) |
| Failover / load balancing | Tak (natywnie od 5.4) | Tak (od Laravel 9, klucz mailers) |
| Cache konfiguracji | Brak (env czytany na żywo) | config:cache — wymaga czyszczenia |
| Testowy transport | null://null |
MAIL_MAILER=log lub array |
Typowe błędy SMTP i jak je naprawić
1. Connection refused / timeout
Najczęstsza przyczyna to zablokowany port wychodzący przez firewall serwera lub hosting. Wielu dostawców hostingu współdzielonego blokuje port 25 i 465 — sprawdź, czy port 587 (STARTTLS) jest dostępny. Możesz to zweryfikować poleceniem telnet smtp.firma.pl 587 lub nc -zv smtp.firma.pl 587.
2. Authentication failed (535)
Kod 535 oznacza odrzucenie danych uwierzytelniających. Najczęstsze przyczyny:
- Niezakodowany znak
@w DSN (użyj%40). - Hasło zawiera znaki specjalne (
#,&,%) — zakoduj je URL-encode lub umieść hasło w cudzysłowach w.env. - Włączone 2FA na koncie pocztowym bez wygenerowania hasła aplikacji.
- Konto SMTP ma włączone ograniczenie IP — serwer aplikacji musi być na białej liście.
3. SSL/TLS handshake failed
Błąd pojawia się, gdy port i protokół szyfrowania są niezgodne. Reguła jest prosta: port 587 → tls (STARTTLS), port 465 → ssl (implicit TLS). Mieszanie tych wartości skutkuje błędem handshake. W Symfony użyj smtps:// dla portu 465, a smtp:// dla 587.
4. Maile trafiają do spamu
To nie błąd konfiguracji SMTP per se, ale często mylony z problemem technicznym. Jeśli połączenie SMTP działa, ale wiadomości lądują w spamie, sprawdź:
- Czy domena nadawcy ma poprawny rekord SPF wskazujący na serwer SMTP.
- Czy skonfigurowano DKIM — podpis kryptograficzny wiadomości.
- Czy adres IP serwera SMTP nie jest na publicznych czarnych listach (MXToolbox Blacklist Check).
- Czy nagłówek
Fromzgadza się z domeną autoryzowaną przez SPF/DKIM.
5. Brak efektu po zmianie .env w Laravel
Klasyczna pułapka: zmieniasz .env na produkcji, ale aplikacja nadal używa starych wartości. Przyczyną jest cache konfiguracji. Wykonaj:
php artisan config:clear && php artisan cache:clear
Jeśli używasz PHP-FPM, może być też konieczny restart procesu (systemctl restart php8.2-fpm), bo OPcache może trzymać stare pliki.
6. Symfony: „No transport found for DSN"
Błąd pojawia się, gdy Symfony nie rozpoznaje schematu w DSN. Upewnij się, że zainstalowałeś odpowiedni pakiet transportu. Dla SMTP wymagany jest symfony/mailer (instalowany domyślnie), ale dla zewnętrznych dostawców (Mailgun, Postmark) potrzebne są osobne paczki, np. composer require symfony/mailgun-mailer.
Bezpieczne zarządzanie zmiennymi środowiskowymi SMTP
Dane dostępowe do SMTP to wrażliwe dane uwierzytelniające — ich wyciek może skutkować nadużyciem konta pocztowego do spamu lub phishingu. Kilka zasad, które warto wdrożyć:
- Nigdy nie commituj
.envdo repozytorium. Plik.envpowinien być w.gitignore— w Symfony i Laravel jest tam domyślnie, ale warto to sprawdzić. - Używaj osobnego konta SMTP dla każdego środowiska. Produkcja, staging i dev powinny mieć różne dane — błąd w dev nie powinien wysyłać maili do prawdziwych użytkowników.
- Ogranicz uprawnienia konta SMTP do minimum — tylko wysyłka, bez dostępu do skrzynki odbiorczej.
- Rotuj hasła regularnie, szczególnie po odejściu pracownika z dostępem do konfiguracji.
- Na środowiskach chmurowych rozważ AWS Secrets Manager, HashiCorp Vault lub podobne narzędzia zamiast pliku
.env.
Jeśli korzystasz z własnej skrzynki SMTP i narzędzia takiego jak MailerPRO, dane dostępowe znajdziesz w panelu konta — możesz je bezpośrednio wkleić do zmiennych środowiskowych bez konieczności ręcznego konfigurowania serwera pocztowego.
Środowisko deweloperskie: jak nie wysyłać maili przypadkowo
Na etapie developmentu ostatnią rzeczą, jakiej chcesz, jest przypadkowe wysłanie maila do prawdziwego klienta. Oba frameworki mają wbudowane mechanizmy przechwytywania wiadomości.
Symfony: null transport i Mailer profiler
Ustaw MAILER_DSN=null://null w .env.local — wiadomości zostaną „pochłonięte" bez wysyłki. Alternatywnie użyj smtp://localhost:1025 z lokalnym narzędziem jak Mailpit lub MailHog, które przechwytuje SMTP i wyświetla wiadomości w przeglądarce. Symfony Web Profiler pokazuje też wysłane maile w zakładce „Mails".
Laravel: log driver i array driver
W Laravel ustaw MAIL_MAILER=log — wiadomości trafią do pliku storage/logs/laravel.log zamiast przez SMTP. Dla testów jednostkowych użyj MAIL_MAILER=array, który przechowuje wiadomości w pamięci i pozwala na asercje w testach (Mail::assertSent()). Możesz też skonfigurować Mailpit jako lokalny serwer SMTP i używać MAIL_HOST=localhost MAIL_PORT=1025.
Konfiguracja SMTP w Symfony i Laravel nie jest skomplikowana, ale wymaga precyzji — jeden błędny znak w DSN lub zapomniane config:clear może sprawić, że maile przestają wychodzić bez żadnego oczywistego komunikatu. Zacznij od weryfikacji połączenia na poziomie sieci, sprawdź format DSN (szczególnie kodowanie @), upewnij się, że port i szyfrowanie są spójne, a na koniec przetestuj wysyłkę z CLI przed deployem. Jeśli szukasz prostego sposobu na własny serwer SMTP bez konfigurowania całej infrastruktury pocztowej, MailerPRO daje gotowe dane SMTP do wklejenia bezpośrednio w zmienne środowiskowe — bez dodatkowej konfiguracji po stronie serwera.
📨 Wypróbuj Mailer PRO
Wysyłaj mailing z własnych skrzynek SMTP — bez prowizji od liczby maili. Zachowujesz pełną kontrolę nad reputacją domeny.
Zobacz cennik Jak to działa


