Zum Hauptinhalt springen
Wenn ein Paket eine eigene Versandmethode für E-Mails bereitstellen soll, registrieren Sie in Laravel eine Erzeugungsmethode für einen Symfony-Transport. Mailables oder den Laravel-Mailer müssen Sie dafür nicht neu bauen. Diese Seite basiert auf der Implementierung von Laravel Framework v13.39.0. Die Grundlagen des Versands finden Sie unter E-Mails versenden, die Vorbereitung des Providers unter Paketentwicklung.

Mailer-Name und Transport-Name trennen

Mail::mailer('receipts') liest mail.mailers.receipts und ruft die Erzeugungsmethode auf, die zu transport gehört. MailManager::build() übergibt diesen Transport an den Laravel-Mailer und setzt bei der normalen Auflösung per Name auch globale Werte wie den Absender. Eigene Erzeugungsmethoden haben Vorrang vor den eingebauten Transports. Überschreiben Sie daher nicht smtp oder ses, sondern verwenden Sie einen paketspezifischen Transport-Namen.

Den Registrierungsweg ohne externen Versand aufbauen

Prüfen Sie zuerst nicht die API des Zustelldienstes, sondern Konfiguration und Auflösungsweg. Legen Sie in src/Transport/MemoryTransport.php des Pakets Acme\Postbox eine Klasse an, die vom Standard-ArrayTransport erbt.
ArrayTransport::send() speichert die Symfony-SentMessage in einer internen Collection. Mit messages() können Sie sie einsehen, mit flush() die Aufzeichnungen dieser Instanz löschen. Das Label dient dazu, zu prüfen, ob die Konfiguration bei der Erzeugungsmethode angekommen ist.
Dieses Beispiel stellt keine Mails nach außen zu. Es dient nur dem Lernen von Registrierung und Tests. Die Aufzeichnungen im Speicher werden weder prozessübergreifend geteilt noch dauerhaft gespeichert und belegen auch nicht, dass die Mail beim Empfänger angekommen ist. Wenn Sie nur im Speicher aufzeichnen möchten, genügt der eingebaute array-Transport.

Beim verzögert geladenen Manager registrieren

Die offizielle Dokumentation ruft Mail::extend() in boot() auf. In Paketen können Sie zusätzlich Hooks nach der Service-Auflösung verwenden, damit Anwendungen, die keine Mails nutzen, den Manager nicht unnötig erzeugen. Legen Sie src/PostboxServiceProvider.php an.
Den Provider laden Sie zum Beispiel über die automatische Paketerkennung.
  • Der MailServiceProvider von Laravel ist ein verzögerter Provider und registriert mail.manager als Singleton. Auch die Mail-Facade löst diesen Schlüssel auf.
  • callAfterResolving() wartet auf die spätere Auflösung, wenn der Manager noch nicht aufgelöst ist, und wendet die Erweiterung auf einen bereits aufgelösten Manager sofort an. Bereits erzeugte Mailer werden dadurch nicht ersetzt.
  • Machen Sie den eigenen Provider, der die Erweiterung registriert, nicht einfach zu einem DeferrableProvider. Es darf kein eigener Transport angefordert werden, bevor der Hook registriert ist.
  • Dieses Beispiel registriert den Hook im eigenen boot(). Eine Startreihenfolge, in der andere Provider den eigenen Mailer schon vorher nutzen, wird nicht abgedeckt. Planen Sie die Registrierungsreihenfolge und versenden Sie keine Mails in register().

Die Argumente von extend nicht mit anderen Managern verwechseln

MailManager::createSymfonyTransport() ruft eine eigene Erzeugungsmethode mit nur einem Konfigurations-Array auf. Anders als bei Cache::extend() gibt es nicht zwei Argumente für Anwendung und Konfiguration. Der Rückgabewert ist ein Transport, der TransportInterface implementiert, und weder ein Laravel-Mailer noch ein Sendeergebnis. Außerdem speichert MailManager::extend() die Closure nur und bindet sie nicht erneut an den Manager. Das Beispiel verwendet eine statische Closure mit expliziten Argumenten, damit es nicht von der Bedeutung von $this abhängt. Wenn Sie einen Client für eine externe API benötigen, wenden Sie in dieser Erzeugungsmethode die Konfiguration an und übergeben ihn an den Transport. Senden Sie bei der Registrierung nichts, und gestalten Sie den Code so, dass sich Konfiguration und Ziele nicht vermischen, wenn mehrere Mailer denselben Transport-Namen verwenden.

Mailer in der nutzenden Anwendung hinzufügen

Fügen Sie dem bestehenden mailers-Array in config/mail.php die folgenden zwei Einträge hinzu. Das Paket sollte weder den Standard des Nutzers noch das gesamte mailers-Array überschreiben.
Soll er zum Standard werden, tragen Sie in MAIL_MAILER nicht den Transport-Namen ein, sondern den Mailer-Namen receipts. Bei expliziter Angabe können Sie ihn nutzen, ohne den bestehenden Standard zu ändern.
Dieses raw() ist ein Beispiel, um den synchronen Sendeweg ohne View zu prüfen. Auch beim normalen Versand mit Mailables übergibt der Mailer die Nachricht nach dem Erzeugen des Inhalts an denselben Transport.

Grenzen beim Bau eines echten Zustell-Transports

Für eine Implementierung, die an einen externen Dienst zustellt, können Sie gemäß der offiziellen Dokumentation von Symfony\Component\Mailer\Transport\AbstractTransport erben und doSend(SentMessage $message): void sowie __toString(): string implementieren. Die obige Registrierungsmethode lässt sich unverändert verwenden. Mailer::sendSymfonyMessage() von Laravel ruft send() des Transports auf. Bricht ein MessageSending-Listener den Versand ab oder gibt der Transport null zurück, liefert auch send() von Laravel kein Sendeergebnis. Werten Sie das bloße Ausbleiben einer Exception nicht als erfolgreiche Zustellung. Auch eine zurückgegebene SentMessage garantiert nicht, dass die Mail im Postfach des Empfängers angekommen ist.

Konfigurationsänderung und erneute Auflösung des Mailers unterscheiden

MailManager hält Mailer und deren Transport pro Mailer-Name vor. Eine Konfigurationsänderung oder eine erneute Registrierung per extend() allein aktualisiert bereits erzeugte Objekte nicht.
$same ist derselbe Mailer wie $before. Für $after, das nach purge('receipts') abgerufen wird, wird der Transport aus der neuen Konfiguration erzeugt. Die geprüfte Implementierung von purge() entfernt lediglich die Referenz aus dem internen Array des Managers. Das alte $before und Services, denen dieser Mailer bereits injiziert wurde, werden nicht ersetzt. Externe Verbindungen werden dabei auch nicht explizit beendet.

Übergeordnete Mailer wie failover werden separat vorgehalten

failover und roundrobin lesen die Konfiguration der untergeordneten Mailer-Namen, erzeugen die Transports direkt und binden sie in den übergeordneten Transport ein. Die Instanzen von Mail::mailer() der untergeordneten Mailer werden dabei nicht geteilt. Wenn Sie daher die Konfiguration eines untergeordneten Mailers ändern und purge('receipts') aufrufen, wird der interne Transport eines bereits aufgelösten übergeordneten Mailers nicht aktualisiert. Auch der betroffene übergeordnete Mailer muss erneut aufgelöst werden. Selbst forgetMailers() kann Referenzen auf übergeordnete Mailer, die der Aufrufer bereits hält, nicht ersetzen. Nehmen Sie in Produktion die Aktualisierung des Konfigurations-Caches und den Neustart langlebiger Worker in den regulären Deployment-Ablauf auf. Verstehen Sie die erneute Auflösung nicht als gleichzeitige Aktualisierung aller Prozesse, und lesen Sie auch Lebensdauer von Services.

Nicht nur Mail::fake, sondern den echten Erzeugungsweg testen

Mail::fake() dient dazu, den Versand und das Einreihen von Mailables in die Queue aufzuzeichnen. MailFake::mailer() löst keinen echten Mailer auf, sondern zeichnet den Mailer-Namen auf und gibt das Fake selbst zurück. Auch raw() führt keinen Versand aus. Assertions zum Versand von Mailables können daher erfolgreich sein, obwohl der eigene Transport gar nicht registriert ist. Leiten Sie in der Testumgebung des Pakets wie folgt ohne Fake über den echten Manager und Transport. Da der Transport in diesem Beispiel nichts nach außen sendet, können Sie den Registrierungsweg ohne Risiko einer echten Zustellung prüfen. Ein Hilfs-Provider wird zuerst geladen, damit auch der Fall eines bereits aufgelösten Managers geprüft wird.
Dieser Test prüft die Grenzen von Registrierung, Konfiguration, synchronem Versand und vorgehaltenen Objekten. Bei einem echten Zustell-Transport prüfen Sie Payload und Fehlerantworten zusätzlich mit Mocks oder Fakes des API-Clients. Wenn Sie den Versand über Queues unterstützen, trennen Sie auch die Tests für Queues und stellen Sie sicher, dass der Provider auch auf Worker-Seite geladen wird.

Herangezogene Primärquellen

Zuletzt geändert am 30. Oktober 2026