phplaravellaravel-queuelaravel-mail

Changing Laravel password reset mail to queued


I'm struggling with queues in Laravel as I never used them before. I'm overriding the default reset password email with help of the toMailUsing method and a dedicated service provider:

class MailServiceProvider extends ServiceProvider
{
    public function boot()
    {
        ResetPassword::toMailUsing(function ($notifiable, $token) {
            $url = url(route('password.reset', ['token' => $token, 'email' => $notifiable->getEmailForPasswordReset()]));
            dispatch(new SendEmail($url, $notifiable));
        });
    }
}

Here is my SendEmail job class:

class SendEmail implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    protected $user;
    protected $url;

    public function __construct($url, $user)
    {
        $this->user = $user;
        $this->url = $url;
    }

    
    public function handle()
    {
        $email = new ResetPassword($this->url, $this->user);
        Mail::to($this->user->email)->send($email);
    }
}

And the mailable itself:

class ResetPassword extends Mailable
{
    use Queueable, SerializesModels;
    protected $url;
    protected $user;


    public function __construct($url, $user)
    {
        $this->url = $url;
        $this->user = $user;
    }


    public function build()
    {
        return $this->markdown('emails.password_reset', ['url' => $this->url, 'user' => $this->user]);
    }
}

Where is the problem? I successfully queue the job and receive email, but get an error:

Trying to get property 'view' of non-object

Stack trace: https://flareapp.io/share/87nOGYM5#F59

Here is my previous, working code:

//Provider
ResetPassword::toMailUsing(function ($notifiable, $token) {
    $url = url(route('password.reset', ['token' => $token, 'email' => $notifiable->getEmailForPasswordReset()]));
    return new ResetPasswordMail($url, $notifiable);
});

//Mailable
class ResetPassword extends Mailable
{
    use Queueable, SerializesModels;
    protected $url;
    protected $user;


    public function __construct($url, $user)
    {
        $this->url = $url;
        $this->user = $user;
    }


    public function build()
    {
        $address = 'noreply@' . config('app.domain');
        $name = 'Lorem ipsum';
        $subject = config('app.name') . ' - Próba zresetowania hasła';

        $this->to($this->user)->subject($subject)->from($address, $name)->markdown('emails.password_reset', ['url' => $this->url, 'user' => $this->user]);
    }
}

I would really appreciate any help.


Solution

  • This looks very complicated, so I'm not sure where your problem is. But if it helps, here's what I do to send a queued custom password reset email.

    1. Illuminate\Auth\Passwords\CanResetPassword::sendPasswordResetNotification() is inherited by the User model; override it to use your custom notification.

      <?php
      
      namespace App;
      
      use App\Notifications\UserPasswordReset;
      
      class User extends Authenticatable
      {
          public function sendPasswordResetNotification(string $token): void
          {
              $url = route('password.reset', [
                  'token' => $token,
                  'email' => $this->getEmailForPasswordReset()
              ]);
              $this->notify(new UserPasswordReset($url));
          }
      }
      
    2. Build a custom notification and make sure it is queuable

      <?php
      
      namespace App\Notifications;
      
      use App\Mail\UserPasswordReset as UserPasswordResetMailable;
      use App\User;
      use Illuminate\Bus\Queueable;
      use Illuminate\Contracts\Queue\ShouldQueue;
      
      class UserPasswordReset extends Notification implements ShouldQueue
      {
          use Queueable;
      
          protected $url;
      
          public function __construct(string $url)
          {
              $this->url = $url;
          }
      
          public function via(User $notifiable)
          {
              return ["mail"];
          }
      
          public function toMail(User $notifiable)
          {
              return new UserPasswordResetMailable($this->url, $notifiable);
          }
      }
      
    3. Your existing mailable should work fine, though here's what mine looks like:

      <?php
      
      namespace App\Mail;
      
      use App\User;
      use Illuminate\Bus\Queueable;
      use Illuminate\Mail\Mailable;
      use Illuminate\Queue\SerializesModels;
      
      class UserPasswordReset extends Mailable
      {
          use Queueable;
          use SerializesModels;
      
          public $user;
          public $url;
      
          public function __construct(string $url, User $user)
          {
              $this->user = $user;
              $this->url = $url;
          }
      
          public function build()
          {
              return $this->
                  ->with(["message" => $this])
                  ->to($this->user->email)
                  ->from(config("mail.from.address"))
                  ->subject(__("Password Reset"))
                  ->text("email.reset_plaintext")
                  ->view("email.reset_html");
          }
      }
      

    No service provider needed, no job class needed.