Vai al contenuto principale
Per fornire da un pacchetto un metodo di invio email personalizzato, registra in Laravel il codice che crea il Transport di Symfony. Non è necessario ricreare le Mailable o il Mailer di Laravel. Questa pagina esamina l’implementazione di Laravel Framework v13.39.0. Per l’invio di base consulta Invio di email, per la preparazione del provider consulta Sviluppo di pacchetti.

Separare il nome del Mailer dal nome del Transport

Mail::mailer('receipts') legge mail.mailers.receipts e invoca la callback di creazione corrispondente a transport. MailManager::build() passa quel Transport al Mailer di Laravel e, nella normale risoluzione per nome, imposta anche il mittente globale e altri valori. Le callback di creazione personalizzate hanno la precedenza sui Transport integrati. Non sovrascrivere smtp o ses: usa un nome di Transport specifico del pacchetto.

Costruire il percorso di registrazione senza invii esterni

Per prima cosa verifica la configurazione e il percorso di risoluzione, non l’API di destinazione. In src/Transport/MemoryTransport.php del pacchetto Acme\Postbox, crea una classe che estende l’ArrayTransport standard.
ArrayTransport::send() salva il SentMessage di Symfony in una Collection interna. Puoi consultarlo con messages() e cancellare i messaggi registrati da quell’istanza con flush(). L’etichetta serve a verificare che la configurazione sia arrivata alla callback di creazione.
Questo esempio non consegna le email all’esterno. Serve solo per imparare la registrazione e i test. I messaggi registrati in memoria non sono condivisi tra processi né resi persistenti, e non confermano la consegna ai destinatari. Se ti serve solo registrare in memoria, il Transport integrato array è sufficiente.

Registrare sul Manager caricato in modo differito

La documentazione ufficiale chiama Mail::extend() in boot(). In un pacchetto puoi combinarlo con un hook successivo alla risoluzione del servizio, così da non creare inutilmente il Manager nelle applicazioni che non usano la posta. Crea src/PostboxServiceProvider.php.
Carica il provider ad esempio tramite il rilevamento automatico dei pacchetti.
  • Il MailServiceProvider di Laravel è un provider differito e registra mail.manager come singleton. Anche la facade Mail risolve questa chiave.
  • Se il Manager non è ancora stato risolto, callAfterResolving() attende la risoluzione successiva; se è già stato risolto, applica subito l’estensione. Non sostituisce i Mailer già creati.
  • Evita di rendere semplicemente DeferrableProvider anche il tuo provider che registra l’estensione. Fai in modo che il Transport personalizzato non venga richiesto prima della registrazione dell’hook.
  • In questo esempio l’hook viene registrato nel tuo boot(). Non è garantito un ordine di avvio in cui altri provider usino il Mailer personalizzato prima di quel momento. Progetta l’ordine di registrazione e non inviare email all’interno di register().

Non confondere gli argomenti di extend con quelli di altri Manager

MailManager::createSymfonyTransport() invoca la callback di creazione personalizzata con un solo array di configurazione. Non sono i due argomenti applicazione e configurazione di Cache::extend(). Il valore restituito è un Transport che implementa TransportInterface, non un Mailer di Laravel né un risultato di invio. Inoltre MailManager::extend() si limita a conservare la closure e non la ricollega al Manager. Nell’esempio si usano closure statiche e argomenti espliciti per non dipendere dall’interpretazione di $this. Se serve un client per un’API esterna, applica la configurazione in questa callback di creazione e passalo al Transport. Non effettuare invii al momento della registrazione e progetta il codice in modo che configurazioni e destinazioni non si mescolino quando più Mailer usano lo stesso nome di Transport.

Aggiungere i Mailer nell’applicazione che usa il pacchetto

Aggiungi le due voci seguenti all’array mailers esistente in config/mail.php. Il pacchetto non deve sovrascrivere il valore predefinito dell’utente né l’intero array mailers.
Se vuoi renderlo predefinito, in MAIL_MAILER va indicato il nome del Mailer receipts, non il nome del Transport. Specificandolo esplicitamente puoi usarlo senza modificare il valore predefinito esistente.
Questo raw() è un esempio per verificare il percorso di invio sincrono senza preparare una view. Anche nel normale invio con le Mailable, il Mailer genera il contenuto e poi lo passa allo stesso Transport.

Confini nella creazione di un vero Transport di consegna

Per un’implementazione che consegna a un servizio esterno, seguendo la documentazione ufficiale puoi estendere Symfony\Component\Mailer\Transport\AbstractTransport e implementare doSend(SentMessage $message): void e __toString(): string. Il metodo di registrazione visto sopra resta invariato. Mailer::sendSymfonyMessage() di Laravel chiama send() del Transport. Se un listener di MessageSending annulla l’invio o il Transport restituisce null, anche send() di Laravel non restituisce un risultato di invio. Non considerare la sola assenza di eccezioni come prova di una consegna riuscita. Anche se viene restituito un SentMessage, non c’è garanzia che il messaggio abbia raggiunto la casella del destinatario.

Distinguere la modifica della configurazione dalla nuova risoluzione del Mailer

MailManager conserva i Mailer e i relativi Transport per nome del Mailer. Modificare la configurazione o registrare di nuovo extend() non aggiorna gli oggetti già creati.
$same è lo stesso Mailer di $before. In $after, ottenuto dopo purge('receipts'), il Transport viene creato dalla nuova configurazione. L’implementazione di purge() esaminata si limita a rimuovere il riferimento dall’array interno del Manager. Il vecchio $before e i servizi in cui quel Mailer è già stato iniettato non vengono sostituiti. Non è nemmeno un’operazione che chiude esplicitamente le connessioni esterne.

Anche i Mailer padre come failover vengono conservati separatamente

failover e roundrobin leggono la configurazione dei nomi dei Mailer figli, creano direttamente i Transport e li incorporano nel Transport padre. Non condividono le istanze Mail::mailer() dei figli. Per questo, anche se modifichi la configurazione di un figlio e chiami purge('receipts'), il Transport interno del Mailer padre già risolto non viene aggiornato. Occorre risolvere di nuovo anche il Mailer padre da aggiornare. Nemmeno forgetMailers() può sostituire i riferimenti al Mailer padre già conservati dal codice chiamante. In produzione, includi nella normale procedura di deploy l’aggiornamento della cache di configurazione e il riavvio dei worker di lunga durata. Non considerare la nuova risoluzione come un aggiornamento simultaneo di tutti i processi e consulta anche Ciclo di vita dei servizi.

Testare il vero percorso di creazione, non solo Mail::fake

Mail::fake() serve a registrare l’invio e l’accodamento delle Mailable. MailFake::mailer() non risolve il vero Mailer: registra il nome del Mailer e restituisce il fake stesso. Anche raw() non esegue alcun invio. Le asserzioni sull’invio delle Mailable possono quindi passare anche senza che il Transport personalizzato sia registrato. Nell’ambiente di test del pacchetto, fai passare il codice dal vero Manager e dal vero Transport senza usare il fake, come segue. Il Transport di questo esempio non invia all’esterno, quindi puoi verificare il percorso di registrazione senza rischio di consegne reali. Un provider ausiliario viene caricato per primo, così da verificare anche il caso in cui il Manager sia già stato risolto.
Questo test verifica i confini di registrazione, configurazione, invio sincrono e oggetti conservati. Per un vero Transport di consegna, verifica a parte il payload e le risposte di errore con un mock o un fake del client API. Se offri l’invio tramite coda, separa anche i test delle code e verifica che il provider venga caricato anche lato worker.

Fonti primarie consultate

Ultima modifica il 30 ottobre 2026