> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Registrazione e test dei transport di posta nei pacchetti

> Analizzare il MailManager di Laravel 13 e spiegare la registrazione di un transport da un pacchetto, il passaggio della configurazione, la conservazione e la nuova risoluzione dei Mailer e i percorsi di invio che un fake non può verificare.

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](https://kawax.biz/it/mail.md), per la preparazione del provider consulta [Sviluppo di pacchetti](https://kawax.biz/it/advanced/package-development.md).

## Separare il nome del Mailer dal nome del Transport

| Elemento | Esempio in questa pagina | Ruolo |
| - | - | - |
| Nome del Transport | `acme-memory-mail` | Il metodo di creazione registrato con `extend()` |
| Nome del Mailer | `receipts` / `alerts` | Il nome della configurazione in `mail.mailers` e l'identificatore del Mailer già creato |
| Symfony Transport | `MemoryTransport` | La consegna delle email. In questo esempio si limita a registrarle in memoria |
| Laravel Mailer | `Illuminate\Mail\Mailer` | Generazione del contenuto, eventi di invio, passaggio al Transport e così via |

`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.

```mermaid theme={null}
flowchart TD
    A["Mail::mailer('receipts')"] --> B{"receipts è già stato risolto?"}
    B -->|Sì| C["Restituisce il Mailer conservato"]
    B -->|No| D["Legge mail.mailers.receipts"]
    D --> E["Callback di creazione di acme-memory-mail<br>unico argomento: l'array di configurazione"]
    E --> F["Restituisce un Symfony Transport"]
    F --> G["Crea il Mailer di Laravel e<br>lo conserva con il nome receipts"]
    G --> H["All'invio chiama Transport::send()"]
    C --> H
```

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.

```php theme={null}
<?php

namespace Acme\Postbox\Transport;

use Illuminate\Mail\Transport\ArrayTransport;

class MemoryTransport extends ArrayTransport
{
    public function __construct(
        private readonly string $label,
    ) {
        parent::__construct();
    }

    public function __toString(): string
    {
        return 'acme-memory-mail:'.$this->label;
    }
}
```

`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.

> [!WARNING]
>
> 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](https://kawax.biz/it/advanced/package-service-resolution.md), così da non creare inutilmente il Manager nelle applicazioni che non usano la posta.

Crea `src/PostboxServiceProvider.php`.

```php theme={null}
<?php

namespace Acme\Postbox;

use Acme\Postbox\Transport\MemoryTransport;
use Illuminate\Mail\MailManager;
use Illuminate\Support\ServiceProvider;
use Symfony\Component\Mailer\Transport\TransportInterface;

class PostboxServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->callAfterResolving('mail.manager', static function (
            MailManager $manager,
        ): void {
            $manager->extend('acme-memory-mail', static function (
                array $config,
            ): TransportInterface {
                return new MemoryTransport($config['label'] ?? 'mail');
            });
        });
    }
}
```

Carica il provider ad esempio tramite il [rilevamento automatico dei pacchetti](https://kawax.biz/it/advanced/package-discovery.md).

* 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`.

```php theme={null}
'receipts' => [
    'transport' => 'acme-memory-mail',
    'label' => 'receipts-v1',
],

'alerts' => [
    'transport' => 'acme-memory-mail',
    'label' => 'alerts',
],
```

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.

```php theme={null}
use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Mail;

Mail::mailer('receipts')->raw('Receipt body', static function (Message $message): void {
    $message->from('noreply@example.com')
        ->to('reader@example.com')
        ->subject('Receipt');
});

$transport = Mail::mailer('receipts')->getSymfonyTransport();

$recorded = $transport->messages()->sole();
```

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.

| Cosa verificare | Responsabilità del pacchetto |
| - | - |
| Messaggio e destinatari | Passare correttamente all'API di destinazione non solo il testo, ma anche HTML, allegati, CC e BCC e i destinatari dell'Envelope |
| Errori | Non trattare come successo un errore dell'API, ma propagarlo al chiamante |
| Separazione della configurazione | Applicare al Transport endpoint e credenziali di ciascun Mailer |
| Rappresentazione testuale | Non includere credenziali o contenuto delle email in `__toString()` |
| Test | Oltre ai test di registrazione, configurazione e nuova risoluzione, verificare la conversione verso l'API e la gestione degli errori |

`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.

```php theme={null}
use Illuminate\Support\Facades\Mail;

$before = Mail::mailer('receipts');

config(['mail.mailers.receipts.label' => 'receipts-v2']);

$same = Mail::mailer('receipts');

Mail::purge('receipts');

$after = Mail::mailer('receipts');
```

`$same` è lo stesso Mailer di `$before`. In `$after`, ottenuto dopo `purge('receipts')`, il Transport viene creato dalla nuova configurazione.

| Operazione | Effetto |
| - | - |
| `Mail::purge('receipts')` | Rimuove il riferimento al Mailer indicato conservato dal Manager |
| `Mail::forgetMailers()` | Rimuove i riferimenti a tutti i Mailer conservati dal Manager. Le estensioni registrate restano |
| `$transport->flush()` | Cancella i messaggi registrati dal MemoryTransport di questo esempio |

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](https://kawax.biz/it/advanced/package-service-lifetimes.md).

## 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](https://kawax.biz/it/advanced/package-testing.md), 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.

```php theme={null}
<?php

namespace Acme\Postbox\Tests;

use Acme\Postbox\PostboxServiceProvider;
use Acme\Postbox\Transport\MemoryTransport;
use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\ServiceProvider;
use Orchestra\Testbench\TestCase;
use Symfony\Component\Mime\Email;

class MailTransportTest extends TestCase
{
    protected function getPackageProviders($app): array
    {
        return [EarlyMailManagerProvider::class, PostboxServiceProvider::class];
    }

    protected function defineEnvironment($app): void
    {
        $app['config']->set('mail.default', 'receipts');
        $app['config']->set('mail.from', [
            'address' => 'noreply@example.com',
            'name' => 'Postbox',
        ]);
        $app['config']->set('mail.mailers.receipts', [
            'transport' => 'acme-memory-mail',
            'label' => 'receipts-v1',
        ]);
        $app['config']->set('mail.mailers.alerts', [
            'transport' => 'acme-memory-mail',
            'label' => 'alerts',
        ]);
    }

    public function test_registers_on_an_already_resolved_manager(): void
    {
        $this->assertSame(
            $this->app->make('postbox.test.early-manager'),
            $this->app->make('mail.manager'),
        );

        $transport = Mail::mailer('receipts')->getSymfonyTransport();

        $this->assertInstanceOf(MemoryTransport::class, $transport);
        $this->assertSame('acme-memory-mail:receipts-v1', (string) $transport);
    }

    public function test_message_reaches_the_transport(): void
    {
        $mailer = Mail::mailer('receipts');

        $sent = $mailer->raw('Receipt body', static function (Message $message): void {
            $message->to('reader@example.com')->subject('Receipt');
        });

        $transport = $mailer->getSymfonyTransport();

        $this->assertNotNull($sent);
        $this->assertCount(1, $transport->messages());

        $recorded = $transport->messages()->sole();
        $email = $recorded->getOriginalMessage();

        $this->assertInstanceOf(Email::class, $email);
        $this->assertSame('Receipt', $email->getSubject());
        $this->assertSame('Receipt body', $email->getTextBody());
        $this->assertSame('noreply@example.com', $email->getFrom()[0]->getAddress());
        $this->assertSame(
            'reader@example.com',
            $recorded->getEnvelope()->getRecipients()[0]->getAddress(),
        );
    }

    public function test_mailer_names_have_separate_transports(): void
    {
        $receipts = Mail::mailer('receipts');
        $alerts = Mail::mailer('alerts');

        $this->assertNotSame($receipts->getSymfonyTransport(), $alerts->getSymfonyTransport());
        $this->assertSame('acme-memory-mail:alerts', (string) $alerts->getSymfonyTransport());

        $receipts->raw('Receipt body', static function (Message $message): void {
            $message->to('reader@example.com')->subject('Receipt');
        });

        $this->assertCount(1, $receipts->getSymfonyTransport()->messages());
        $this->assertCount(0, $alerts->getSymfonyTransport()->messages());
    }

    public function test_config_change_requires_purging_the_mailer(): void
    {
        $before = Mail::mailer('receipts');

        config(['mail.mailers.receipts.label' => 'receipts-v2']);

        $this->assertSame($before, Mail::mailer('receipts'));
        $this->assertSame('acme-memory-mail:receipts-v1', (string) $before->getSymfonyTransport());

        Mail::purge('receipts');

        $after = Mail::mailer('receipts');

        $this->assertNotSame($before, $after);
        $this->assertSame('acme-memory-mail:receipts-v2', (string) $after->getSymfonyTransport());
        $this->assertSame('acme-memory-mail:receipts-v1', (string) $before->getSymfonyTransport());
    }
}

class EarlyMailManagerProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->app->instance('postbox.test.early-manager', $this->app->make('mail.manager'));
    }
}
```

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](https://kawax.biz/it/queues.md) e verifica che il provider venga caricato anche lato worker.

## Fonti primarie consultate

* [Documentazione ufficiale di Laravel: Transport personalizzati e test delle email (alla data della verifica)](https://github.com/laravel/docs/blob/960eedf0911155ad16326ade49c0ea5d18eda0b8/mail.md#custom-transports)
* [Documentazione ufficiale di Laravel: Sviluppo di pacchetti](https://github.com/laravel/docs/blob/13.x/packages.md)
* [MailManager: creazione, conservazione dei Mailer, purge e Transport composti](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Mail/MailManager.php)
* [MailServiceProvider: binding di MailManager](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Mail/MailServiceProvider.php)
* [ServiceProvider: callAfterResolving](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Support/ServiceProvider.php)
* [Mailer: invio al Transport ed eventi](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Mail/Mailer.php)
* [ArrayTransport: registrazione dei messaggi](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Mail/Transport/ArrayTransport.php)
* [Facade Mail e MailFake](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Support/Facades/Mail.php)
* [MailFake: selezione del Mailer e registrazione delle Mailable](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Support/Testing/Fakes/MailFake.php)
* [Test ufficiali di Laravel: verifica dell'invio con Mailer e ArrayTransport](https://github.com/laravel/framework/blob/v13.39.0/tests/Mail/MailMailerTest.php)


## Related topics

- [Invio di email](https://kawax.biz/it/mail.md)
- [Argomenti avanzati](https://kawax.biz/it/advanced/index.md)
- [Hook di risoluzione dei servizi nei pacchetti](https://kawax.biz/it/advanced/package-service-resolution.md)
- [Store di cache personalizzati e contratti nei pacchetti](https://kawax.biz/it/advanced/package-cache-drivers.md)
- [Estendere e risolvere di nuovo i channel di log nei pacchetti](https://kawax.biz/it/advanced/package-logging.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.