symfony/mailer
Symfony Mailer helps you send emails via SMTP and other transports with a clean API. Build Email/TemplatedEmail messages, add attachments and headers, and integrate with Twig templates for HTML rendering. Configure transports via DSN and send reliably.
Install the package (if not already included in Laravel 8+):
composer require symfony/mailer
Configure your mail transport in .env (updated for security fixes):
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
For API-based transports (e.g., SendGrid, Mailgun), use a DSN with proper encoding:
MAIL_MAILER=smtp
MAILER_DSN=smtp://api:$(urlencode key-123)@mailpit:1025
First use case: Sending a simple email (unchanged):
use Symfony\Component\Mime\Email;
use Symfony\Component\Mailer\MailerInterface;
public function sendWelcomeEmail(MailerInterface $mailer)
{
$email = (new Email())
->from('noreply@example.com')
->to('user@example.com')
->subject('Welcome!')
->text('Thanks for signing up!');
$mailer->send($email);
}
Mail facade (wraps Symfony Mailer):
use Illuminate\Support\Facades\Mail;
Mail::to('user@example.com')->send(new WelcomeEmail());
Mail::to('user@example.com')->send(new WelcomeEmail($user));
$email = (new Email())
->from('sender@example.com')
->to('recipient@example.com')
->subject('Invoice #123')
->text('Please find attached.')
->html('<p>Please find attached.</p>')
->attachmentFromPath('/path/to/invoice.pdf', 'invoice.pdf')
->priority(Email::PRIORITY_HIGH);
$email = (new Email())
->from('noreply@example.com')
->subject('Your Order Update');
// Add recipients dynamically
$email->to('user1@example.com');
$email->cc('manager@example.com');
$email->bcc('archive@example.com');
$email = (new Email())
->replyTo('support@example.com')
->addCustomHeader('X-Priority', '1')
->addCustomHeader('X-Mailer', 'Laravel/Symfony');
use Symfony\Component\Mailer\Transport\TransportInterface;
use Symfony\Component\Mailer\Transport\RoundRobinTransport;
$primary = Transport::fromDsn('smtp://user:pass@smtp.example.com');
$fallback = Transport::fromDsn('smtp://user:pass@backup-smtp.example.com');
$transport = new RoundRobinTransport([$primary, $fallback]);
$mailer = new Mailer($transport);
// SendGrid (ensure API key is URL-encoded in DSN)
$transport = Transport::fromDsn('sendgrid://api:'.urlencode('key-123').'@default');
$mailer = new Mailer($transport);
// Mailgun
$transport = Transport::fromDsn('mailgun://api:'.urlencode('key-123').'@default');
$transport = Transport::fromDsn('null://default');
$mailer = new Mailer($transport); // Emails are discarded (useful for tests).
// app/Mail/WelcomeEmail.php
public function build()
{
return $this->subject('Welcome!')
->view('emails.welcome')
->with(['user' => $this->user]);
}
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bridge\Twig\Mime\BodyRenderer;
$twig = new \Twig\Environment($loader);
$bodyRenderer = new BodyRenderer($twig);
$email = (new TemplatedEmail())
->from('noreply@example.com')
->to('user@example.com')
->subject('Welcome!')
->htmlTemplate('emails/welcome.twig')
->context(['name' => 'John']);
use Symfony\Component\Mailer\EventListener\MessageLoggerListener;
$logger = new \Monolog\Logger('mailer');
$listener = new MessageLoggerListener($logger);
$eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
$eventDispatcher->addSubscriber($listener);
$transport = Transport::fromDsn('smtp://...', $eventDispatcher);
use Symfony\Component\Mailer\EventListener\MessageSentEvent;
$eventDispatcher->addListener(MessageSentEvent::class, function (MessageSentEvent $event) {
$message = $event->getMessage();
info("Sent to: {$message->getTo()[0]}");
});
public function test_welcome_email()
{
Mail::fake();
$this->post('/register', ['email' => 'user@example.com']);
Mail::assertSent(WelcomeEmail::class, function ($mail) {
return $mail->hasTo('user@example.com');
});
}
$transport = Transport::fromDsn('null://default');
$mailer = new Mailer($transport);
$mailer->send($email);
$sentMessages = $transport->getSentMessages();
$this->assertCount(1, $sentMessages);
// Wrong: smtp://user:passhost:port
// Right: smtp://user:pass@host:port
// For API keys: smtp://api:$(urlencode 'key-123')@host:port
-) when using SendmailTransport..env matches the transport:
MAIL_ENCRYPTION=tls # For STARTTLS (port 587)
MAIL_ENCRYPTION=ssl # For implicit SSL (port 465)
RoundRobinTransport for large emails:
$transport = new RoundRobinTransport([$primary, $fallback], null, false);
$email->addCustomHeader('X-MJ-TemplateErrorReporting', 'true');
$transport = Transport::fromDsn('smtp://user:pass@host:port', [
'debug' => true,
]);
$mailer->send($email);
$sentMessages = $transport->getSentMessages();
-) when using SendmailTransport to avoid injection risks:
$email->to('valid@example.com'); // OK
$email->to('-invalid@example.com'); // Rejected by SendmailTransport
How can I help you explore Laravel packages today?