Passer au contenu principal
Pour fournir une méthode d’envoi de mail propre depuis un package, enregistrez auprès de Laravel la création d’un Transport Symfony. Il n’est pas nécessaire de réécrire les Mailables ni le Mailer de Laravel. Cette page a été vérifiée sur l’implémentation de Laravel Framework v13.39.0. Pour l’envoi de base, consultez Envoi d’e-mails, et pour préparer le provider, consultez Développement de packages.

Distinguer le nom du Mailer du nom du Transport

Mail::mailer('receipts') lit mail.mailers.receipts et appelle la création correspondant à transport. MailManager::build() transmet ce Transport au Mailer de Laravel et, lors d’une résolution par nom classique, configure aussi l’expéditeur global, entre autres. Les créations personnalisées sont choisies avant les Transports intégrés. N’écrasez pas smtp ou ses : utilisez un nom de Transport propre au package.

Construire le chemin d’enregistrement sans envoi externe

Commencez par vérifier la configuration et le chemin de résolution plutôt que l’API de destination. Dans src/Transport/MemoryTransport.php du package Acme\Postbox, créez une classe qui hérite de l’ArrayTransport standard.
ArrayTransport::send() enregistre le SentMessage de Symfony dans une Collection interne. Vous pouvez le consulter avec messages() et effacer les enregistrements de cette instance avec flush(). Le label sert à vérifier que la configuration atteint bien la création.
Cet exemple ne livre aucun mail vers l’extérieur. Il sert uniquement à apprendre l’enregistrement et les tests. Les enregistrements en mémoire ne sont ni partagés entre processus ni persistés, et ne confirment pas la réception par le destinataire. Si votre seul objectif est d’enregistrer en mémoire, le Transport intégré array suffit.

Enregistrer sur un Manager chargé de manière différée

La documentation officielle appelle Mail::extend() dans boot(). Dans un package, vous pouvez combiner cela avec les hooks après résolution de service afin d’éviter de créer inutilement le Manager dans les applications qui n’utilisent pas le mail. Créez src/PostboxServiceProvider.php.
Chargez le provider via la découverte automatique de packages ou un mécanisme équivalent.
  • Le MailServiceProvider de Laravel est un provider différé qui enregistre mail.manager en singleton. La façade Mail résout également cette clé.
  • callAfterResolving() attend la résolution ultérieure si le Manager n’est pas encore résolu, et applique immédiatement l’extension à un Manager déjà résolu. Il ne remplace pas les Mailers déjà générés.
  • Évitez de rendre simplement DeferrableProvider votre propre provider qui enregistre l’extension. Faites en sorte que le Transport personnalisé ne soit pas demandé avant l’enregistrement du hook.
  • Cet exemple enregistre le hook dans son propre boot(). Il ne garantit pas un ordre de démarrage où un autre provider utiliserait le Mailer personnalisé plus tôt. Concevez l’ordre d’enregistrement et n’envoyez pas de mail dans register().

Ne pas confondre les arguments d’extend avec ceux d’autres Managers

MailManager::createSymfonyTransport() appelle la création personnalisée avec un seul argument : le tableau de configuration. Ce ne sont pas les deux arguments application et configuration de Cache::extend(). La valeur de retour est un Transport implémentant TransportInterface, et non un Mailer de Laravel ni un résultat d’envoi. De plus, MailManager::extend() se contente de conserver la closure et ne la relie pas au Manager. L’exemple utilise une closure statique et des arguments explicites afin de ne pas dépendre de l’interprétation de $this. Si vous avez besoin d’un client pour une API externe, appliquez la configuration dans cette création et transmettez-le au Transport. N’envoyez rien au moment de l’enregistrement, et concevez le code de façon à ne pas mélanger configurations et destinations lorsque plusieurs Mailers partagent le même nom de Transport.

Ajouter les Mailers dans l’application cliente

Ajoutez les deux entrées suivantes au tableau mailers existant de config/mail.php. Le package ne doit pas écraser la valeur par défaut de l’utilisateur ni l’ensemble de mailers.
Pour en faire la valeur par défaut, indiquez dans MAIL_MAILER le nom du Mailer receipts, et non le nom du Transport. En le désignant explicitement, vous pouvez l’utiliser sans modifier la valeur par défaut existante.
Ce raw() est un exemple permettant de vérifier le chemin d’envoi synchrone sans préparer de vue. Lors d’un envoi classique avec un Mailable, le Mailer génère le contenu puis le transmet au même Transport.

Limites lors de la création d’un vrai Transport de livraison

Pour une implémentation qui livre vers un service externe, vous pouvez, conformément à la documentation officielle, hériter de Symfony\Component\Mailer\Transport\AbstractTransport et implémenter doSend(SentMessage $message): void et __toString(): string. La méthode d’enregistrement ci-dessus reste identique. Mailer::sendSymfonyMessage() de Laravel appelle send() sur le Transport. Si un listener MessageSending annule l’envoi ou si le Transport retourne null, le send() de Laravel ne retourne pas non plus de résultat d’envoi. Ne vous fiez pas à la seule absence d’exception pour juger qu’une livraison a réussi. Même si un SentMessage est retourné, cela ne garantit pas que le mail a atteint la boîte du destinataire.

Distinguer changement de configuration et nouvelle résolution du Mailer

MailManager conserve le Mailer et son Transport par nom de Mailer. Un changement de configuration ou un nouvel enregistrement via extend() ne met pas à jour les objets déjà générés.
$same est le même Mailer que $before. Pour $after, obtenu après purge('receipts'), le Transport est créé à partir de la nouvelle configuration. L’implémentation de purge() examinée se contente de retirer la référence du tableau interne du Manager. L’ancien $before et les services dans lesquels ce Mailer a déjà été injecté ne sont pas remplacés. Ce n’est pas non plus une opération qui ferme explicitement les connexions externes.

Les Mailers parents comme failover sont aussi conservés séparément

failover et roundrobin lisent la configuration des noms de Mailers enfants, créent directement les Transports et les intègrent dans le Transport parent. Ils ne partagent pas les instances Mail::mailer() des enfants. Par conséquent, même si vous modifiez la configuration d’un enfant et appelez purge('receipts'), le Transport interne d’un Mailer parent déjà résolu n’est pas mis à jour. Il faut aussi résoudre à nouveau le Mailer parent concerné. Même forgetMailers() ne peut pas remplacer les références au Mailer parent déjà conservées par le code appelant. En production, intégrez la mise à jour du cache de configuration et le redémarrage des workers de longue durée dans votre procédure de déploiement habituelle. Ne considérez pas la nouvelle résolution comme une mise à jour globale de tous les processus, et consultez également le cycle de vie des services.

Tester le vrai chemin de création, pas seulement Mail::fake

Mail::fake() sert à enregistrer l’envoi et la mise en queue des Mailables. MailFake::mailer() ne résout pas de vrai Mailer : il enregistre le nom du Mailer et retourne le fake lui-même. raw() n’effectue pas non plus d’envoi. Les assertions d’envoi de Mailables peuvent donc réussir même sans Transport personnalisé enregistré. Dans votre environnement de test de package, faites passer les appels par le vrai Manager et le vrai Transport, sans fake, comme ci-dessous. Le Transport de cet exemple n’envoie rien vers l’extérieur, ce qui permet de vérifier le chemin d’enregistrement sans risque de livraison réelle. Un provider auxiliaire est chargé en premier pour vérifier aussi le cas où le Manager est déjà résolu.
Ce test vérifie les limites de l’enregistrement, de la configuration, de l’envoi synchrone et des objets conservés. Pour un vrai Transport de livraison, vérifiez séparément la charge utile et les réponses d’échec à l’aide d’un mock ou d’un fake du client API. Si vous proposez l’envoi via queue, séparez aussi les tests des queues et vérifiez que le provider est bien chargé côté worker.

Sources primaires consultées

Dernière modification le 30 octobre 2026