SteB
SteB

Reputation: 2007

Laravel error when sending email notification

I've created a custom command to run on a schedule and email a notification.

My Command class handle method:

public function handle()
{
    $sql = "SELECT * FROM Licences WHERE (Expired = 1)";
    $list = DB::select($sql);

    return (new NotifyExpiredLicences($list))->toMail('[email protected]');
}

My notification method:

public function toMail($notifiable)
{
    return (new MailMessage)
            ->subject('Clients with Expired Licences')
            ->markdown('vendor/notifications/expiredlicences', 
                ['clients' => $this->list, 'toname' => 'Me']);
}

Whenever I test this by running it manually with php artisan email:expired-licences I get the following error Object of class Illuminate\Notifications\Messages\MailMessage could not be converted to int from my command class in the handle method.

However, the preview of my email works fine & displays as expected:

Route::get('/notification', function () {
    return (new SendExpiredLicences())->handle();
});

If I remove the return statement from my handle() method, then although I get no errors, neither in my console or in storage\logs, also the preview stops working.

At this point I'm sure I've missed something important from the way this is supposed to be done, but after going through the Laravel docs and looking at online tutorials/examples, I've no idea what.

Upvotes: 0

Views: 2483

Answers (2)

miken32
miken32

Reputation: 42736

Ok just to save more commenting, here's what I'd recommend doing. This is all based on the Laravel docs, but there are multiple ways of doing it, including what you've used above. I don't really think of them as "right and wrong," more "common and uncommon."

Console\Kernel.php: I'd keep this mostly as-is, but pass the email to the command from a config file, rather than having it fixed in the command.

use App\Console\Commands\SendExpiredLicences;
…
protected function schedule(Schedule $schedule) 
{
    $recipient = config('myapp.expired.recipient');
    $schedule->command(SendExpiredLicences::class, [$recipient])
        ->weekdays()
        ->at('08:30');
}

config/myapp.php:

<?php

return [
    'expired' => [
        'recipient' => '[email protected]',
    ],
];

App\Console\Commands\SendExpiredLicences.php: update the command to accept the email address as an argument, use on-demand notifications, and get rid of preview() method. Neither the command or the notification need to know about the client list, so don't build it yet.

<?php
namespace App\Console\Commands;

use App\Console\Command;
use App\Notifications\NotifyExpiredLicences;
use Illuminate\Support\Facade\Notification;

class SendExpiredLicences extends Command
{
  protected $signature = 'email:expired-licences {recipient}';
  protected $description = 'Email a list of expired licences to the given address';

  public function handle()
  {
      $recip = $this->argument('recipient');
      Notification::route('email', $recip)->notify(new NotifyExpiredLicences());
  }
}

App\Notifications\NotifyExpiredLicences.php: the toMail() method should pass the notifiable object (i.e. the user getting notified) along, because the mailable will be responsible for adding the To address before the thing is sent.

<?php
namespace App\Notifications;

use App\Mail\ExpiredLicenses;
use Illuminate\Notifications\Notification;

class NotifyExpiredLicences extends Notification
{
    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        return (new ExpiredLicenses($notifiable));
    }
}

App\Mail\ExpiredLicences.php: since the mail message actually needs the list of clients, this is where we build it. We get the recipient here, either from the user's email or the anonymous object.

<?php
namespace App\Mail;

use App\Models\Client;
use Illuminate\Notifications\AnonymousNotifiable;

class ExpiredLicences extends Mailable
{
    private $email;

    public function __construct(private $notifiable)
    {
        // this allows the notification to be sent to normal users
        // not just on-demand
        $this->email = $notifiable instanceof AnonymousNotifiable
            ? $notifiable->routeNotificationFor('mail')
            : $notifiable->email;
    }

    public function build()
    {
        // or whatever your object is
        $clients = Client::whereHas('licenses', fn($q)=>$q->whereExpired(1));

        return $this
            ->subject('Clients with Expired Licences')
            ->markdown(
                'emails.expiredlicences', 
                ['clients' => $clients, 'toname' => $this->notifiable->name ?? 'Admin']
            )
            ->to($this->email);
    }
}

For previewing with the browser routes\web.php:

Route::get('/notification', function () {
    // create a dummy AnonymousNotifiable object for preview
    $anon = Notification::route('email', '[email protected]');
    return (new ExpiredLicencesNotification())
        ->toMail($anon);
});

Upvotes: 1

SteB
SteB

Reputation: 2007

I've got everything working - though not entirely sure it's the "Laravel way". If anyone's got suggestions for improving it - add a comment or new answer and I'll try it out.

Console\Kernel.php:

protected function schedule(Schedule $schedule) 
{
    $schedule->command('email:expired-licences')
                ->weekdays()
                ->at('08:30');
}

App\Console\Commands\SendExpiredLicences.php:

class SendExpiredLicences extends Command
{
  protected $signature = 'email:expired-licences';
  protected $description = 'Email a list of expired licences to Admin';
  private $mail;

  public function _construct()
  {
      $clients = DB::select("[Insert SQL here]");
      $this->mail = (new NotifyExpiredLicences($clients))->toMail('[email protected]');
      parent::__construct();
  }

  public function handle()
  {
    Mail::to('[email protected]')->send($this->mail);
    return 0;
  }

  public function preview()
  {
    return $this->mail;
  }
}

App\Notifications\NotifyExpiredLicences.php:

class NotifyExpiredLicences extends Notification
{
    public function __construct(protected $clients)
    {
    }

    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        return (new Mailable($this->clients));
    }
}

App\Mail\ExpiredLicences.php:

class ExpiredLicences extends Mailable
{
  public function __construct(private $clients)
  {
  }

  public function build()
  {
    return $this
            ->subject('Clients with Expired Licences')
            ->markdown('emails/expiredlicences', 
                ['clients' => $this->clients, 'toname' => 'Admin']);
  }
}

resources\views\emails\expiredlicences.blade.php:

@component('mail::message')

  # Hi {!! $toname !!},

@component('mail::table')
    | Client        | Expired  |
    | ------------- | --------:|
    @foreach ($clients as $client)
    |{!! $client->CompanyName !!} | {!! $client->Expired !!}|
    @endforeach
@endcomponent

<hr />
Thanks, {!! config('app.name') !!}
@endcomponent

For previewing with the browser routes\web.php:

  Route::get('/notification', function () {
      return (new SendExpiredLicences())->preview();
  });

Upvotes: 1

Related Questions