Comment envoyer des mails avec Symfony
Pour envoyer des mails avec Symfony, on utilise le composant Mailer : il construit le message, le transmet à un serveur SMTP ou à l’API d’un service d’envoi, et peut passer par une file d’attente pour ne pas ralentir vos pages. Voici la configuration complète, de l’installation aux templates Twig, avec la gestion des erreurs et les points de délivrabilité à ne pas oublier.
Installer et configurer le transport
- Installer le composant. Le composant Mime, qui sert à construire les messages, est installé avec lui :
composer require symfony/mailer
- Définir le transport dans la variable
MAILER_DSN, dans.env.localen développement et dans les variables d’environnement du serveur en production, jamais dans un fichier versionné avec un mot de passe :# Serveur SMTP (caractères spéciaux du mot de passe encodés en URL) MAILER_DSN=smtp://utilisateur:motdepasse@smtp.example.com:587 # Développement : n'envoie rien MAILER_DSN=null://null
- Pour un service d’envoi (Brevo, Mailgun, Amazon SES, Postmark…), installez le bridge correspondant, qui permet d’utiliser leur API plutôt que le SMTP. Exemple avec Brevo :
composer require symfony/brevo-mailer # puis dans .env.local : # MAILER_DSN=brevo+api://VOTRE_CLE_API@default
En développement, le plus pratique est un faux serveur SMTP comme Mailpit, qui capture tous les messages dans une interface web : rien ne part chez un vrai destinataire. La barre de débogage de Symfony affiche aussi les e-mails envoyés pendant une requête.
Envoyer un premier e-mail
- Construire le message et l’envoyer avec le service
MailerInterface, injecté dans un contrôleur ou un service :<?php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Email; use Symfony\Component\Routing\Attribute\Route; class ContactController extends AbstractController { #[Route('/contact/test', name: 'contact_test')] public function test(MailerInterface $mailer): Response { $email = (new Email()) ->from(new Address('contact@example.com', 'Mon application')) ->to('client@example.com') ->replyTo('support@example.com') ->subject('Votre demande a bien été reçue') ->text('Nous revenons vers vous rapidement.') ->html('<p>Nous revenons vers vous rapidement.</p>'); $mailer->send($email); return new Response('E-mail envoyé'); } }
Renseignez toujours une version texte en plus du HTML : certains clients de messagerie l’affichent, et son absence pèse dans l’évaluation anti-spam. Pour joindre un fichier, la méthode attachFromPath() prend un chemin de fichier.
Des e-mails HTML avec Twig
- Utiliser
TemplatedEmail, qui rend un template Twig avec des variables (le bundle Twig doit être installé) :use Symfony\Bridge\Twig\Mime\TemplatedEmail; $email = (new TemplatedEmail()) ->from(new Address('contact@example.com', 'Mon application')) ->to($utilisateur->getEmail()) ->subject('Confirmez votre adresse e-mail') ->htmlTemplate('emails/confirmation.html.twig') ->textTemplate('emails/confirmation.txt.twig') ->context([ 'prenom' => $utilisateur->getPrenom(), 'lien' => $lienDeConfirmation, ]); $mailer->send($email); - Écrire le template : les variables passées dans
context()y sont disponibles, ainsi qu’une variableemailqui donne accès au message :{# templates/emails/confirmation.html.twig #} <p>Bonjour {{ prenom }},</p> <p>Pour activer votre compte, confirmez votre adresse :</p> <p><a href="{{ lien }}">Confirmer mon adresse</a></p>
Les clients de messagerie gèrent mal les feuilles de style externes : le filtre inline_css (paquet twig/cssinliner-extra) recopie les styles directement dans les balises.
Gérer les erreurs et envoyer en arrière-plan
- Attraper les erreurs d’envoi : un serveur SMTP indisponible ou une clé d’API invalide lèvent une exception qui, non attrapée, renvoie une erreur 500 à l’utilisateur :
use Symfony\Component\Mailer\Exception\TransportExceptionInterface; try { $mailer->send($email); } catch (TransportExceptionInterface $e) { $this->logger->error('Échec d\'envoi', ['erreur' => $e->getMessage()]); // informer l'utilisateur ou réessayer plus tard } - Envoyer de façon asynchrone avec Messenger : la page répond immédiatement et l’e-mail part dans un processus séparé, avec de nouvelles tentatives automatiques en cas d’échec. Une fois Messenger installé et un transport
asyncconfiguré :# config/packages/messenger.yaml framework: messenger: routing: 'Symfony\Component\Mailer\Messenger\SendEmailMessage': async
En mode asynchrone, $mailer->send() ne lève plus d’erreur de transport : les échecs se suivent dans les logs du worker (php bin/console messenger:consume async), qui doit tourner en permanence sur le serveur.
Délivrabilité : ce qui se joue hors du code
- SPF, DKIM et DMARC : ces enregistrements DNS du domaine expéditeur prouvent que votre serveur ou votre service est autorisé à envoyer en son nom. Sans eux, Gmail et Outlook classent vite vos messages en spam, ou les refusent.
- Une adresse d’expéditeur sur votre domaine, cohérente avec le service d’envoi configuré. N’envoyez pas « de la part » d’une adresse Gmail.
- Séparer transactionnel et marketing : confirmations de commande et newsletters n’ont pas la même réputation ; idéalement, des flux ou sous-domaines distincts.
L’envoi d’e-mails fait partie de presque toutes les applications que nous développons en Symfony : inscription, notifications, factures. Pour un projet, voyez notre agence Symfony à Lille ou notre page développement d’application web sur mesure.