Laravel 7

Shopping : les emails

Dans cet article on va mettre en œuvre la création et l’envoi des emails. Pour une boutique en ligne c’est fondamental parce qu’il y a un certain nombre d’étapes qui nécessitent l’envoi d’un email. Mais il serait contre-productif et vraiment pas écologique d’en utiliser trop. Il faut savoir se limiter à l’essentiel. On peut prévoir un email de bienvenue lorsqu’un client s’inscrit. On doit aussi prévoir un email lorsqu’une commande est validée. Par contre il n’est pas nécessaire d’en prévoir un lorsqu’on prépare la commande si l’envoi est rapide, d’autre part il serait dommage d’envoyer un email pour indiquer que la commande est partie si on passe par le service Colissimo qui peut lui-même s’en occuper avec en plus le numéro de suivi.

Vous pouvez télécharger un ZIP du projet ici.

Créer des emails

Lorsqu’on veut créer des emails on est vite confronté au fait qu’ils doivent être lus par un nombre considérable d’outils différents et pour chacun deux il faut avoir un rendu correct. Autant on est arrivés pour les sites à une syntaxe moderne à peu près homogène, autant pour les emails on a l’impression de faire un voyage aux débuts de l’informatique. Je me souviens de mes débuts lorsque je faisais des mises en page avec des tableaux, eh bien c’est encore un peu ça avec les emails.

Heureusement il existe des outils pour nous faciliter la vie. En particulier mjml.

On a un éditeur en ligne :

En plus il existe un package npm pour mix. Alors on va l’installer :

npm i laravel-mix-mjml -D

Il faut mettre à jour webpack.mix.js :

const mix = require('laravel-mix');
require('laravel-mix-mjml');

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .mjml();

Les sources doivent être par défaut dans resources/mail.

Et la sortie générée dans resources/views/mail.

Pour les tests je vous conseille d’utiliser Mailtrap.

Nouvel inscrit

L’email

On va prévoir un email à envoyer lorsqu’un client s’inscrit sur la boutique :

<mjml>
  <mj-body background-color="#FFF">
    <mj-section padding-bottom="20px" padding-top="20px">
      <mj-column width="100%">
        <mj-image src="{{ asset('images/logo.png') }}" alt="" align="center" border="none" width="300px" padding-left="0px" padding-right="0px" padding-bottom="10px" padding-top="10px"></mj-image>
      </mj-column>
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="0px" padding-top="0">
      <mj-column width="100%">
        <mj-text align="center" font-size="13px" color="#ABCDEA" font-family="Ubuntu, Helvetica, Arial, sans-serif" padding-left="25px" padding-right="25px" padding-bottom="18px" padding-top="28px">BONJOUR
          <p style="font-size:16px; color:white">{{ $user->name }} {{ $user->firstname }}</p>
        </mj-text>
      </mj-column>
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column width="100%">
        <mj-divider border-color="#ffffff" border-width="2px" border-style="solid" padding-left="20px" padding-right="20px" padding-bottom="0px" padding-top="0"></mj-divider>
        <mj-text align="center" color="#FFF" font-size="13px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="28px" padding-top="28px"><span style="font-size:20px; font-weight:bold">Merci d'avoir créé votre compte client</span>
        </mj-text>
      </mj-column>
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column width="100%">
        <mj-divider border-color="#ffffff" border-width="2px" border-style="solid" padding-left="20px" padding-right="20px" padding-bottom="0px" padding-top="0"></mj-divider>
        <mj-text align="center" color="#FFF" font-size="13px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="20px" padding-top="28px"><span style="font-size:20px; font-weight:bold">Votre code d'accès</span>
        </mj-text>
        <mj-text align="center" color="#FFF" font-size="13px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="28px"><span style="font-size:20px; font-weight:bold">Adresse e-mail : {{ $user->email }}</span>
        </mj-text>
      </mj-column>
    </mj-section>
    <mj-section background-color="#568feb" padding-bottom="15px">
      <mj-column>
        <mj-text align="center" color="#FFF" font-size="17px" font-family="Ubuntu, Helvetica, Arial, sans-serif" padding-left="25px" padding-right="25px" padding-bottom="0px"><strong>Conseils de sécurité</strong></mj-text>
        <mj-text color="#FFF" font-size="15px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="20px" padding-top="10px">
          <ul>
            <li>
              Vos informations de compte doivent rester confidentielles.
            </li>
            <li>Ne les communiquez jamais à qui que ce soit.</li>
            <li>Changez votre mot de passe régulièrement.</li>
            <li>
              Si vous pensez que quelqu'un utilise votre compte illégalement, veuillez nous prévenir immédiatement.
            </li>
          </ul>
        </mj-text>
      </mj-column>
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="0px" padding-top="0">
      <mj-column width="100%">
        <mj-text align="center" color="#FFF" font-size="15px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="20px" padding-top="20px">Vous pouvez dès à présent passer commande sur notre boutique
        </mj-text>
      </mj-column>
    </mj-section>
    <mj-section background-color="#FFF" padding-bottom="0px" padding-top="0">
      <mj-column width="100%">
        <mj-text align="center" color="#555" font-size="15px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-top="20px">{{ $shop->name }}</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

On va le compiler :

npm run dev

On trouve l’email généré ici :

La classe Mailable

On crée une classe Mailable Registered :

php artisan make:mail Registered

<?php

namespace App\Mail;

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

class Registered extends Mailable
{
    use Queueable, SerializesModels;

    public $shop;

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

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from($this->shop->email, $this->shop->name)
                    ->subject('Bienvenue !')
                    ->view('mail.registered', ['user' => auth()->user()]);
    }
}

Le contrôleur

On ajoute une méthode registered dans le contrôleur RegisterController :

use App\Models\{ User, Shop };
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Mail\Registered;

...

protected function registered(Request $request, $user)
{
    $shop = Shop::firstOrFail();
    Mail::to($user)->send(new Registered($shop));

    $admins = User::whereAdmin(true)->get();
    foreach($admins as $admin) {
        // Là on prévoira de notifier les administrateurs
    }        

    return redirect(route('adresses.create'))->with('message', config('messages.registered'));
}

On redirige directement le client sur la page de création d’une adresse.

Si tout se passe bien on doit expédier un email dans ce genre :

C’est sobre et lisible.

Nouvelle commande

Quand une nouvelle commande est créée on va envoyer un email au client avec le récapitulatif et aussi aux administrateurs en version simplifiée.

L’email pour le client

On va créer l’email pour le client :

<mjml>
  <mj-head>
    <mj-style>
      a:link { color: lightgrey; }
      a:visited { color: white; }
      a:hover { color: grey; }
    </mj-style>
  </mj-head>
  <mj-body background-color="#FFF">
    <mj-section padding-bottom="20px" padding-top="20px">
      <mj-column width="100%">
        <mj-image src="{{ asset('images/logo.png') }}" alt="" align="center" border="none" width="300px" padding-left="0px" padding-right="0px" padding-bottom="10px" padding-top="10px"></mj-image>
      </mj-column>
    </mj-section>
    
    <mj-section background-color="#356cc7" padding-bottom="20px" padding-top="0">
      <mj-column width="100%">
        <mj-text align="center" color="#FFF" font-size="20px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-top="28px">Commande référence <strong>{{ $order->reference }}</strong>
        </mj-text>
        <mj-text align="center" color="#FFF" font-size="20px" font-family="Helvetica" padding-left="25px" padding-right="25px">Bonjour {{ $user->firstname . ' ' . $user->name }}
        </mj-text>
        <mj-text align="center" color="#FFF" font-size="20px" font-family="Helvetica" padding-left="25px" padding-right="25px">
        Merci d'avoir effectué un achat sur notre boutique
        </mj-text>
      </mj-column>
    </mj-section> 

    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column width="100%">
        <mj-divider border-color="#FFF" border-width="2px" border-style="solid" padding-left="20px" padding-right="20px" padding-bottom="0px" padding-top="0"></mj-divider>
        <mj-text align="center" color="#FFF" font-size="18px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-top="28px">Détails de la commande
        </mj-text>
        <mj-table color="#FFF" font-size="14px">
          <tr style="border-bottom:1px solid #ecedee;text-align:left;padding:15px 0;">
            <th style="padding: 0 15px 0 0;">Produit</th>
            <th style="padding: 0 15px;">Prix TTC</th>
          </tr>

          @foreach ($order->products as $item)
          <tr>
            <td style="padding: 0 15px 0 0;">{{ $item->name }}&nbsp({{ $item->quantity }}&nbsp @if($item->quantity > 1) exemplaires) @else exemplaire) @endif</td>
            <td style="padding: 0 15px;">{{ number_format($item->total_price_gross, 2, ',', ' ') }} €</td>
          </tr>
          @endforeach
          <tr>
            <td style="padding: 15px;"></td>
            <td style="padding: 15px;"></td>
          </tr>

          <tr style="background-color: #25a;">
            <td style="padding: 10px;">Livraison en Colissimo</td>
            <td style="padding: 0 10px 0 10px;">{{ number_format($order->shipping, 2, ',', ' ') }} €</td>
          </tr>
          @if($order->tax > 0)
          <tr style="background-color: #25a">
            <td style="padding: 10px;">TVA à {{ $order->tax * 100 }}%</td>
            <td style="padding: 0 10px 0 10px;">{{ number_format($order->tva, 2, ',', ' ') }} €</td>
          </tr>
          @endif

          <tr style="background-color: #25a">
            <td style="padding: 10px;">Total TTC</td>
            <td style="padding: 0 10px 0 10px;">{{ number_format($order->totalOrder, 2, ',', ' ') }} €</td>
          </tr>
        </mj-table>
        <mj-text color="#FFF" font-size="16px" font-family="Helvetica" padding-left="25px" padding-bottom="20px" padding-right="25px">
          <br /> Date : {{ $order->created_at->format('d/m/Y') }}<br /><br /> Paiement : {{ $order->payment_text }}
        </mj-text>
      </mj-column>

    </mj-section>

    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column width="100%">
        <mj-divider border-color="#FFF" border-width="2px" border-style="solid" padding-left="20px" padding-right="20px" padding-bottom="0px" padding-top="0"></mj-divider>
      </mj-column>
    </mj-section>

    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column>
        <mj-text color="#FFF" font-size="16px" font-family="Helvetica" padding-left="25px" padding-bottom="20px" padding-right="25px">
          <strong>Adresse de facturation @if($order->adresses->count() === 1 && !$order->pick) et de livraison @endif</strong><br /><br /> 
          @isset($order->adresses->first()->name)
          {{ $order->adresses->first()->civility . ' ' . $order->adresses->first()->name . ' ' . $order->adresses->first()->firstname }}<br />
          @endisset 
          @if($order->adresses->first()->company)
          {{ $order->adresses->first()->company }}<br />
          @endif
          {{ $order->adresses->first()->address }}<br />
          @if($order->adresses->first()->addressbis)
          {{ $order->adresses->first()->addressbis }}<br />
          @endif 
          @if($order->adresses->first()->bp)
          {{ $order->adresses->first()->bp }}<br />
          @endif
          {{ $order->adresses->first()->postal . ' ' . $order->adresses->first()->city }}<br />
          {{ $order->adresses->first()->country->name }}<br />
          {{ $order->adresses->first()->phone }}<br />
        </mj-text>
      </mj-column>
      
      
      <mj-column>
        
        <mj-text color="#FFF" font-size="16px" font-family="Helvetica" padding-left="25px" padding-bottom="20px" padding-right="25px">
          @if($order->adresses->count() > 1)
            <strong>Adresse de livraison</strong><br /><br /> 
            @isset($order->adresses->first()->name)
            {{ $order->adresses->last()->civility . ' ' . $order->adresses->last()->name . ' ' . $order->adresses->last()->firstname }}<br />
            @endisset 
            @if($order->adresses->last()->company)
            {{ $order->adresses->last()->company }}<br />
            @endif
            {{ $order->adresses->last()->address }}<br />
            @if($order->adresses->last()->addressbis)
            {{ $order->adresses->last()->addressbis }}<br />
            @endif @if($order->adresses->last()->bp)
            {{ $order->adresses->last()->bp }}<br />
            @endif
            {{ $order->adresses->last()->postal . ' ' . $order->adresses->last()->city }}<br />
            {{ $order->adresses->last()->country->name }}<br />
            {{ $order->adresses->last()->phone }}<br />
          @endif
        </mj-text>
        
      </mj-column>
      
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column width="100%">
                <mj-divider border-color="#FFF" border-width="2px" border-style="solid" padding-left="20px" padding-right="20px" padding-bottom="0px" padding-top="0"></mj-divider>
        <mj-text color="#FFF" font-size="16px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-top="10px">

          @if($order->payment === 'cheque')
          	 <p>Veuillez nous envoyer un chèque avec :</p>
        <ul>
          <li>montant du règlement : <strong>{{ number_format($order->totalOrder, 2, ',', ' ') }} €</strong></li>     
          <li>payable à l'ordre de <strong>{{ $shop->name }}</strong></li>
          <li>à envoyer à <strong>{{ $shop->address }}</strong></li>
          <li>n'oubliez pas d'indiquer votre référence de commande <strong>{{ $order->reference }}</strong></li>
        </ul>
          @if($order->pick)
          <p><strong>Vous pourrez venir chercher votre commande dès réception du paiement.</strong></p>
        @else
          <p><strong>Votre commande vous sera envoyée dès réception du paiement.</strong>.</p>
        @endif
        Pour toute question ou information complémentaire merci de contacter notre <a href="mailto:{{ $shop->email }}">support client</a>.
          @endif

          @if($order->payment === 'mandat')
          	 <p>Vous avez choisi de payer par mandat administratif. Ce type de paiement est réservé aux administrations.</p>
        <p>Vous devez envoyer votre mandat administratif à :</p>
        <p><strong>{{ $shop->name }}</strong></p>
        <p><strong>{{ $shop->address }}</strong></p>
        <p>Vous pouvez aussi nous le transmettre par e-mail à cette adresse : <strong>{{ $shop->email }}</strong></p>
        <p>N'oubliez pas d'indiquer votre référence de commande <strong>{{ $order->reference }}</strong>.</p>
          @if($order->pick)
          <p><strong>Vous pourrez venir chercher votre commande dès réception du mandat.</strong></p>
        @else
          <p><strong>Votre commande vous sera envoyée dès réception du mandat.</strong>.</p>
        @endif
        Pour toute question ou information complémentaire merci de contacter notre <a href="mailto:{{ $shop->email }}">support client</a>.
          @endif

           @if($order->payment === 'virement')
        <p>Veuillez effectuer un virement sur notre compte :</p>
        <ul>
          <li>montant du virement : <strong>{{ number_format($order->totalOrder, 2, ',', ' ') }} €</strong></li>
          <li>titulaire : <strong>{{ $shop->holder }}</strong></li>  
          <li>BIC : <strong>{{ $shop->bic }}</strong></li>
          <li>IBAN : <strong>{{ $shop->iban }}</strong></li>
          <li>banque : <strong>{{ $shop->bank }}</strong></li>
          <li>adresse banque : <strong>{{ $shop->bank_address }}</strong></li>
          <li>n'oubliez pas d'indiquer votre référence de commande <strong>{{ $order->reference }}</strong></li>
        </ul>
          @if($order->pick)
          <p><strong>Vous pourrez venir chercher votre commande dès réception du paiement.</strong></p>
        @else
          <p><strong>Votre commande vous sera envoyée dès réception du paiement.</strong>.</p>
        @endif
        Pour toute question ou information complémentaire merci de contacter notre <a href="mailto:{{ $shop->email }}">support client</a>.
          @endif

          @if($order->payment === 'carte')
        <p>Vous avez choisi de payer par carte bancaire. Nous vous transmettrons un email de confirmation dès que votre réglement aura été effectué.</p>
          @if($order->pick)
          <p><strong>Vous pourrez venir chercher votre commande dès réception du paiement.</strong></p>
        @else
          <p><strong>Votre commande vous sera envoyée dès réception du paiement.</strong>.</p>
        @endif
        Pour toute question ou information complémentaire merci de contacter notre <a href="mailto:{{ $shop->email }}">support client</a>.
          @endif

        </mj-text>
      </mj-column>
    </mj-section>

    <mj-section background-color="#FFF" padding-bottom="0px" padding-top="0">
      <mj-column width="100%">
        <mj-text align="center" color="#555" font-size="15px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="20px" padding-top="20px">{{ $shop->name }}</mj-text>
      </mj-column>
    </mj-section>

    <mj-section background-color="#36c" padding-bottom="0px" padding-top="0">
      <mj-column width="100%">
      <mj-text color="#ddd"align="center"  font-size="18px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-top="20px">
        Conditions générales de vente       
        </mj-text>
        <mj-text align="justify" color="#ddd" font-size="12px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="20px" >
        {!! $page->text !!}        
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

On trouve l’email généré ici après compilation :

La classe Mailable

On crée une classe Mailable Ordered :

php artisan make:mail Ordered

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use App\Models\{ Order, Shop, page };

class Ordered extends Mailable
{
    use Queueable, SerializesModels;

    public $shop;
    public $order;
    public $page;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(Shop $shop, Order $order, Page $page)
    {
        $this->shop = $shop;
        $this->order = $order;
        $this->page = $page;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from($this->shop->email, $this->shop->name)
                    ->subject('Confirmation de commande')
                    ->view('mail.ordered', ['user' => auth()->user()]);
    }
}

L’email pour les administrateurs

On va créer l’email pour les administrateurs :

<mjml>
  <mj-body background-color="#FFF">
    <mj-section padding-bottom="20px" padding-top="20px">
      <mj-column width="100%">
        <mj-image src="{{ asset('images/logo.png') }}" alt="" align="center" border="none" width="300px" padding-left="0px" padding-right="0px" padding-bottom="10px" padding-top="10px"></mj-image>
      </mj-column>
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column width="100%">
        <mj-text align="center" color="#FFF" font-size="20px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-top="28px">Nouvelle commande
        </mj-text>
        <mj-text align="center" color="#FFF" font-size="20px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="28px" padding-top="10px">du client {{ $user->firstname . ' ' . $user->name }}
        </mj-text>
      </mj-column>
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column width="100%">
        <mj-divider border-color="#ffffff" border-width="2px" border-style="solid" padding-left="20px" padding-right="20px" padding-bottom="0px" padding-top="0"></mj-divider>
        <mj-text align="center" color="#FFF" font-size="20px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="20px" padding-top="28px">Détails de la commande
        </mj-text>        
         <mj-text color="#FFF" font-size="18px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-top="28px">Référence : {{ $order->reference }}        
          </mj-text>
        <mj-text color="#FFF" font-size="18px" font-family="Helvetica" padding-left="25px" padding-right="25px">
          Date : {{ $order->created_at->format('d/m/Y') }}
          </mj-text>
        <mj-text color="#FFF" font-size="18px" font-family="Helvetica" padding-left="25px" padding-right="25px">
          Paiement : {{ $order->payment_text }}
          </mj-text>
        <mj-text color="#FFF" font-size="18px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="30px">
         Total : {{ number_format($order->totalOrder, 2, ',', ' ') }} € 
          </mj-text>
      </mj-column>
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="0px" padding-top="0">
      <mj-column width="100%">
  <mj-text align="center" container-background-color="#FFF" color="#555" font-size="15px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="20px" padding-top="20px">{{ $shop->name }}</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

On trouve l’email généré ici après compilation :

La classe Mailable

On crée une classe Mailable NewOrder :

php artisan make:mail NewOrder

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use App\Models\{ Order, Shop, User };

class NewOrder extends Mailable
{
    use Queueable, SerializesModels;

    public $shop;
    public $order;
    public $user;

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

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from($this->shop->email, $this->shop->name)
                    ->subject('Nouvelle commande')
                    ->view('mail.neworder');
    }
}

Les contrôleur

Dans le contrôleur OrderController on ajoute le code pour l’envoi des emails :

use App\Models\ { Address, Country, Shop, State, Product, User, Page };
use Illuminate\Support\Facades\Mail;
use App\Mail\{ NewOrder, Ordered };

...

public function store(Request $request, Shipping $ship)
{
    ...

    // Notification à l'administrateur
    $shop = Shop::firstOrFail();
    $admins = User::whereAdmin(true)->get();
    foreach($admins as $admin) {
        Mail::to($admin)->send(new NewOrder($shop, $order, $user));
        // On ajoutera une notification ici
    }        
            
    // Notification au client
    $page = Page::whereSlug('conditions-generales-de-vente')->first();
    Mail::to($request->user())->send(new Ordered($shop, $order, $page));

    return redirect(route('commandes.confirmation', $order->id));
}

On devrait envoyer les deux emails, celui pour le client (je l’ai réduit pour la copie mais c’est encore lisible) :

Et celui pour les administrateurs :

Alerte stock produit

On va aussi envoyer un email aux administrateurs lorsqu’un produit atteint sa quantité d’alerte telle que définie dans la base de données.

L’email

On crée l’email :

<mjml>
  <mj-body background-color="#FFF">
    <mj-section padding-bottom="20px" padding-top="20px">
      <mj-column width="100%">
        <mj-image src="{{ asset('images/logo.png') }}" alt="" align="center" border="none" width="300px" padding-left="0px" padding-right="0px" padding-bottom="10px" padding-top="10px"></mj-image>
      </mj-column>
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column width="100%">
        <mj-text align="center" color="#FFF" font-size="20px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-top="28px">Alerte stock
        </mj-text>
        <mj-text align="center" color="#FFF" font-size="20px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="28px" padding-top="10px">du produit {{ $product->name }}
        </mj-text>
      </mj-column>
    </mj-section>
    <mj-section background-color="#356cc7" padding-bottom="5px" padding-top="0">
      <mj-column width="100%">
        <mj-divider border-color="#FFF" border-width="2px" border-style="solid" padding-left="20px" padding-right="20px" padding-bottom="0px" padding-top="0"></mj-divider>
        <mj-text align="center" color="#FFF" font-size="20px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="20px" padding-top="28px">Quantité restante : {{ $product->quantity }}
        </mj-text>
      </mj-column>
    </mj-section>
    <mj-section background-color="#FFF" padding-bottom="0px" padding-top="0">
      <mj-column width="100%">
        <mj-text align="center" color="#555" font-size="15px" font-family="Helvetica" padding-left="25px" padding-right="25px" padding-bottom="20px" padding-top="20px">{{ $shop->name }}</mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

On trouve l’email généré ici :

La classe Mailable

On crée une classe Mailable ProductAlert :

php artisan make:mail ProductAlert

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use App\Models\{ Shop, Product };

class ProductAlert extends Mailable
{
    use Queueable, SerializesModels;
    
    public $shop;
    public $product;

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

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from($this->shop->email, $this->shop->name)
                    ->subject('Alerte produit')
                    ->view('mail.productalert');
    }
}

Le contrôleur

On ajoute du code  dans le contrôleur OrderController :

use App\Mail\{ NewOrder, ProductAlert, Ordered };

...

public function store(Request $request, Shipping $ship)
{
    

    // Enregistrement des produits
    foreach($items as $row) {
        ...
        // Alerte stock
        if($product->quantity <= $product->quantity_alert) {
            $shop = Shop::firstOrFail();
            $admins = User::whereAdmin(true)->get();
            foreach($admins as $admin) {
                Mail::to($admin)->send(new ProductAlert($shop, $product));
            }  
        }
    }

    ...
}

Optimisation

L’envoi d’email prend du temps et il faut envisager une optimisation.

On peut utiliser un système de file d’attente (queue) comme c’est expliqué dans la documentation. On utilise la commande queue au lieu de send.

Vous devez choisir un driver pour mémoriser les tâches : database, redis…

Evidemment ça impose d’utiliser une routine (worker) sur le serveur, c’est à dire un processus PHP qui tourne en tâche de fond et qui effectue les travaux (jobs) un par un en respectant les options définies. On lance cette routien avec une commande Artisan :

php artisan queue:work

Ca a pour effet de créer une instance de notre application qui va rester permanente. Alors évidemment attention si vous faires une modification, il faudra relancer la commande à mojns d’utiliser plutôt la commande :

php artisan queue:listen

Cette fois une instance est crée pour chaque tâche, on n’a donc pas à se soucier des mises à jour.

Il est nécessaire de prévoir un log des tâches qui ont échoué.

Tout ce qui concerne cette question est largement documenté ici.

On peut aussi utiliser un service tiers pour l’envoi des emails mais c’est une autre histoire…

Il faut aussi s’assurer d’une bonne délivrabilité des emails à une époque où on devient paranos avec les spams.

Personnellement je suis hébergé chez O2Switch depuis longtemps et ils ont une infrastructure intéressante pour le SMTP.

Conclusion

Notre boutique sait maintenant expédier des emails aux clients et aux administrateurs. On a bien avancé dans le frontend. On achèvera son codage dans le prochain article.

 

 

Print Friendly, PDF & Email

Leave a Reply