Salta al contenuto principale

Gestire una newsletter con i jobs di Laravel

Profile picture for user luca77king

La gestione di una newsletter è una delle possibili funzionalità richieste dalle applicazioni moderne. Se la incontriamo nelle specifiche di un progetto, dobbiamo tenere a mente che l'invio sincrono delle email può rallentare la risposta del server e l'esperienza utente.

In questo articolo, vedremo come gestire efficacemente l'invio delle email utilizzando i jobs di Laravel.

In Laravel, i jobs sono delle classi che rappresentano dei processi asincroni eseguiti in background nel sistema. Un job è una singola unità di lavoro che viene messa in coda e poi processata in un secondo momento da un "worker".

Vengono utilizzati per eseguire attività che richiedono del tempo, come l'invio di email, la generazione di report o l'elaborazione di grandi quantità di dati. Invece di eseguire queste attività immediatamente durante la richiesta HTTP, i jobs vengono processati in un second momento, evitando di bloccare il flusso principale dell'applicazione.

Configurazione dell'applicazione per gestire i jobs

Prima di tutto, assicuriamoci di aver correttamente configurato il nostro database nel file .env del nostro progetto Laravel. Verifichiamo che le credenziali di accesso al database siano corrette e che il driver sia impostato sul database che intendiamo utilizzare, ad esempio MySQL o PostgreSQL.

Il sistema di gestione delle job queues di Laravel richiede la creazione di due tabelle nel database: jobs e failed_jobs.

Per creare queste tabelle, apriamo il nostro terminale e navighiamo nella cartella del nostro progetto Laravel. Quindi, eseguiamo il comando Artisan seguente:

php artisan queue:table

Questo comando genererà una migration per la tabella jobs. Questa tabella conterrà le informazioni sui job in coda, come il tipo di job, i dati del job e lo stato del job.

Successivamente, creiamo la tabella failed_jobs, che sarà utilizzata per registrare i job che non sono riusciti ad essere processati correttamente. Sempre nel nostro terminale, eseguiamo il comando Artisan seguente:

php artisan queue:failed-table

Questo genererà una migration per la tabella failed_jobs. Per creare la tabella nel database, eseguiamo il comando di migrazione:

php artisan migrate

Ora, dovremmo avere le tabelle jobs e failed_jobs nel nostro database.

Successivamente, apriamo il file .env del nostro progetto Laravel. Troviamo la riga che inizia con QUEUE_CONNECTION= e assicuriamoci che sia impostata su database.

QUEUE_CONNECTION=database

Diamo il comando per ripulire la cache del software

php artisan optimize

Questa è la procedura per configurare il sistema di gestione delle code di lavoro nel database in Laravel. Assicuriamoci di aver correttamente configurato il nostro database, creato le tabelle jobs e failed_jobs e impostato il driver della coda nel file .env. 

Creazione della classe personalizzata per gestire l'email

Per creare una classe email personalizzata, possiamo sfruttare il comando make:mail di Artisan. Utilizzando il terminale, eseguiamo il seguente comando:

php artisan make:mail SendEmail

Una volta creata la classe di email, possiamo personalizzare la vista associata al template dell'email. Laravel consente di utilizzare il motore di rendering Blade per creare viste eleganti e flessibili. All'interno della classe SendEmail, troveremo un metodo chiamato build().

Questo metodo ci consente di impostare i dettagli dell'email, tra cui il mittente, il destinatario, l'oggetto e il contenuto dell'email. Possiamo anche definire il template da utilizzare per l'aspetto visivo dell'email. Utilizzando il motore di rendering Blade, possiamo creare una vista con HTML e variabili dinamiche per personalizzare il contenuto dell'email in base alle esigenze specifiche.

Ecco un esempio di come potrebbe essere il metodo build() all'interno della classe SendEmail:

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class SendEmail extends Mailable
{
    use Queueable, SerializesModels;

    public $user;
    public $message;

    /**
     * Create a new message instance.
     *
     * @param  $user
     * @param  $message
     * @return void
     */
    public function __construct($user, $message)
    {
        $this->user = $user;
        $this->message = $message;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from('noreply@example.com')
                    ->subject('Nuova email')
                    ->view('emails.send')
                    ->with([
                        'user' => $this->user,
                        'message' => $this->message,
                    ]);
    }
}

A questo punto possiamo passare alla configurazione del server SMPT

Configurazione del server SMPT

Per inviare effettivamente le email, dobbiamo configurare le credenziali SMTP nel file .env del nostro progetto Laravel. Raccomandiamo di utilizzare Google come provider di servizi SMTP per la sua affidabilità e semplicità di configurazione. Possiamo accedere al pannello di controllo del nostro account Google e generare le credenziali SMTP necessarie. Successivamente, nel file .env, dobbiamo configurare le seguenti variabili di ambiente:

MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=your-email@gmail.com
MAIL_FROM_NAME="${APP_NAME}"

Creazione del job per inviare le emails

Per iniziare, utilizzeremo il comando Artisan di Laravel per generare una nuova classe di job chiamata SendEmailJob. Esegui il seguente comando nella riga di comando:

php artisan make:job SendEmailJob

Questo comando creerà un nuovo file SendEmailJob.php all'interno della cartella app/Jobs del nostro progetto Laravel.

All'interno del file del job generato, dovremo includere la classe SendEmail che abbiamo creato in precedenza per gestire l'invio delle email. Per farlo, importiamo la classe all'inizio del file:

use App\Mail\SendEmail;

Il metodo handle è il cuore del job e dovrà contenere la logica per l'invio effettivo dell'email. All'interno di questo metodo, definiamo il codice che prenderà l'utente destinatario e il messaggio dell'email, e invierà l'email utilizzando la classe SendEmail.

use App\Mail\SendEmail;
use Illuminate\Support\Facades\Mail;

class SendEmailJob implements ShouldQueue
{
    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        // Ottieni l'utente destinatario dell'email
        $user = // Codice per ottenere l'utente

        // Ottieni il messaggio dell'email
        $message = // Codice per ottenere il messaggio

        // Invia l'email utilizzando la classe SendEmail
        Mail::to($user->email)->send(new SendEmail($user, $message));

        // Altri passaggi o operazioni dopo l'invio dell'email
    }
}

Durante l'invio delle email, potrebbero verificarsi delle eccezioni, ad esempio se il server SMTP non è disponibile. Per gestire queste eccezioni, dobbiamo utilizzare i blocchi try-catch per catturare gli errori e prendere le appropriate misure correttive. Ad esempio:

use App\Mail\SendEmail;
use Illuminate\Support\Facades\Mail;
use Exception;

class SendEmailJob implements ShouldQueue
{
    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        try {
            // Codice per ottenere l'utente destinatario dell'email
            $user = // ...
            
            // Codice per ottenere il messaggio dell'email
            $message = // ...
            
            // Invia l'email utilizzando la classe SendEmail
            Mail::to($user->email)->send(new SendEmail($user, $message));
            
            // Altri passaggi o operazioni dopo l'invio dell'email
        } catch (Exception $e) {
            // Gestisci l'eccezione, ad esempio registrandola in un file di log o inviando una notifica all'amministratore
            // ...
        }
    }
}

Inserimento del job nella coda di lavorazione

Per mettere il job nella coda di lavorazione, puoi utilizzare il metodo dispatch fornito dalla classe Dispatchable di Laravel. Questo metodo prende un'istanza del job e lo inserisce nella coda di lavoro corrispondente.

use App\Jobs\SendEmailJob;

class SomeController extends Controller
{
    public function sendEmail()
    {
        // Esempio di dispatch del job nella coda di lavorazione
        SendEmailJob::dispatch();
        
        // Altri passaggi o reindirizzamenti dopo il dispatch del job
    }
}

Nel controller o in qualsiasi altro punto in cui si desidera inviare l'email, richiamiamo il metodo dispatch sul job che abbiamo creato. Possiamo passare eventuali argomenti o parametri necessari al costruttore del job tramite il metodo dispatch. Nel nostro caso, dovremmo passare l'oggetto $user al job, in modo che poi quest'ultimo lo passi a sua volta alla classe SendMail.

Per gestire il ritardo nell'esecuzione di un job, Laravel offre il concetto di "job in coda". Puoi specificare un ritardo prima che un job venga effettivamente eseguito, consentendo di programmare l'esecuzione del job in un momento successivo.

Per aggiungere un ritardo all'esecuzione di un job, puoi utilizzare il metodo delay nel momento in cui lo dispatchi nella coda di lavorazione. Il metodo delay accetta un parametro di tipo DateTime o un intervallo di tempo rappresentato in secondi. Ecco un esempio:

$delay = now()->addMinutes(10); // Esegue il job dopo 10 minuti
SendEmailJob::dispatch($user)->delay($delay);

Nell'esempio sopra, il job SendEmailJob verrà eseguito 10 minuti dopo essere stato inserito nella coda di lavorazione. Puoi personalizzare il ritardo in base alle tue esigenze specifiche.

Ma supponiamo che vogliamo dare la possibilità all'utente di annullare un job che ormai è stato schedulato, come possiamo fare? In teoria basta salvare l'identificativo del job da qualche parte, in modo da gestirlo a seconda delle esigenze. Per ottenere l'id di un job una volta salvato nel database, dobbiamo usare usare una sintassi differente.

use Illuminate\Contracts\Bus\Dispatcher;

$send_email_job = (new SendEmailJob($user))->delay(now()->addMinutes(10));
$dispatcher = app(Dispatcher::class);
$job_id = $dispatcher->dispatch($send_email_job);

In questo ultimo esempio, abbiamo creato un'istanza del job SendEmailJob passando il destinatario al suo costruttore. Utilizziamo quindi il metodo delay per specificare un ritardo di 10 minuti prima dell'esecuzione effettiva del job.

Successivamente, creiamo un'istanza del Dispatcher utilizzando il servizio app() di Laravel. Il Dispatcher è responsabile per la gestione dell'inserimento del job nella coda di lavorazione.

Infine, utilizziamo il metodo dispatch del Dispatcher per eseguire il dispatch del job nella coda di lavorazione. Questo metodo restituisce un identificatore univoco ($job_id) che rappresenta il job appena creato e inserito nella coda. Puoi utilizzare questo ID per monitorare lo stato o gestire il job in seguito, se necessario.

Avvio della coda di lavorazione

Ora possiamo avviare il worker per iniziare ad elaborare i job in coda. Nel nostro terminale, eseguiamo il comando Artisan seguente:

php artisan queue:work

Il worker si metterà in ascolto delle code di lavoro e inizierà ad elaborare i job in coda. Possiamo anche specificare opzioni aggiuntive come --queue per specificare la coda da elaborare, ad esempio --queue=emails se desideriamo elaborare solo i job della coda "emails".