Laravel Email in Job Queue not working except sync - php

I am using Laravel 5.8, I configured an email containing the invoice sent to user after they place an order.
Email is delivered when set QUEUE_DRIVER=sync but when I set it to redis/database and I run the php artisan queue:work redis --tries=5 it shows the queue is processed
Processing: Modules\Checkout\Mail\Invoice
Processed: Modules\Checkout\Mail\Invoice
succesfully and no entry in the failed_jobs either.
CODE:
Modules\Checkout\Jobs\SendInvoiceEmail.php
class SendInvoiceEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function handle()
{
Mail::to($this->order->customer_email)
->send(new Invoice($this->order));
}
}
And this is the Modules\Checkout\Mail\Invoice.php
class Invoice extends Mailable
{
use Queueable, SerializesModels;
public $order;
public function __construct($order)
{
$this->order = $order;
}
public function build()
{
return $this->subject('New order: ', ['id' => $this->order->id]))
->view("emails.invoice");
}
}
Then I am dispatching it in the Controller after Order created
SendInvoiceEmail::dispatch($order);
Can anyone point out if I've missed anything and what I am doing wrong?
I've done the php artisan config:cache and clear:cache restarted the server.
This works just as expected if I set QUEUE_DRIVER=sync in .env instead of QUEUE_DRIVER=redis or database

I had that problem too and solve it putting a key in the config/mail.php file
in mailers['smtp'] I add local_domain equals to my domain (example.com)
The reason is the Swift Mailer class can use this key to put in the HELO field of the email. If you use QUEUE=sync the framework uses the domain in env file, but when you use a different queue it doesn't cache the domain.

Thing is .env or config.php both configurations should work normally.
My main issue was the application would save these settings from .env or config.php to database. then for the next time it looked in database for these smtp configs and anything other than sync tends to fail.
I don't know why it was configured so I removed and the issue was resolved

Related

add job to queue Laravel

i got a problem when execute my queue. Job doesnt work.
.env:
QUEUE_CONNECTION=database_name
Job:
use App\Models\ProfessorSuscriptionHistory;
class CheckSuscription implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
}
public function handle()
{
$suscription_history = new ProfessorSuscriptionHistory();
$suscription_history->user_id = 13;
$suscription_history->type = 'trimestral';
$suscription_history->pdf = 'test.pdf';
$suscription_history->ended_at = Carbon::now()->addMonth(3);
$suscription_history->save();
}
}
My controller what dispatch a Job
use App\Jobs\CheckSuscription;
class TestController extends Controller
{
public function index()
{
CheckSuscription::dispatch()->onQueue('processing');
}
}
i used command php artisan queue:work and php artisan queue:listen but nothing appear in cmd. i migrated jobs to my db using php artisan queue:table and nothing appear in jobs table or failed_jobs table too. How can i fix that? thx!!
Put your QUEUE_CONNECTION=database_name to just QUEUE_CONNECTION=database you dont put the database name you just tell Laravel to use a certain queue. database, redis ...
Queue Connection

Laravel Mailable not finding my aliased views when developed inside a package

My SSH service provider loads views and aliases them to ssh.
public function boot()
{
...
$this->loadViewsFrom(__DIR__ . '/../Resources/views', 'ssh');
...
}
Inside of my Mail directory, I then have a test mail I'm using for debugging purposes as my service provider also has a scheduler running and I want to email after it executes. It looks like this:
class DockerTest extends Mailable
{
use Queueable, SerializesModels;
private User $user;
private Vps $vps;
public function __construct(User $user, Vps $vps)
{
$this->user = $user;
$this->vps = $vps;
}
public function build()
{
return $this->view('ssh:mail.test', [
'user' => $this->user,
'vps' => $this->vps
]);
}
}
However, when ever I try to test this email in php artisan tinker like so:
Mail::to('foo#bar.com')->send(new DockerTest(($vps = Vps::find(1)), $vps->user))
I am hit with an exception
InvalidArgumentException with message 'View [ssh:mail.test] not found.'
I load my routes like so in the boot function of my service provider:
require_once __DIR__ . '/../Http/routes.php';
When I then do this inside that folder:
Route::get('/test', function() { return view('ssh:mail.test', [($vps = Vps::find(1)), $vps->user]); });
It works perfectly fine? Any ideas? I am developing this as a package and my directory layout looks like this:
app
boostrap
...
packages
MyPackage
Ssh
src
Providers
SshServiceProvider.php
Http
Routes.php
Resources
views
mail
test.blade.php
Mail
DockerTest.php
database
migrations
...
...
vendor
...
the problem is in your view loader.
when you load view in the package , you should write two colons like this:
return view('ssh::mail.test')

Queueing mails in Laravel

I would like to send mail to user after creating account on my website and I would like to use queues to send them. I'm using PHP Laravel framework.
My controller handles the request after clicking on "Create account":
class LoginController extends Controller
{
...
public function register(Request $request) {
...
$mail = (new RegisterRequest($user))->onConnection("database")->onQueue("emailsQueue");
Mail::queue($mail);
...
}
}
Then I have this RegisterRequest (mailable) class:
class RegisterRequest extends Mailable
{
use Queueable, SerializesModels;
protected $user;
public function __construct($user)
{
$this->user = $user;
}
public function build()
{
return $this->from('user#example.com')
->to($this->user->email)
->subject("Confirm your Email Address")
->view('emails.register.request')
->with("registration_token", $this->user->registration_token);
}
}
As you can see, I am using relational database to store jobs. And really, after calling LoginController's register method, a job is saved to database. But it can't be processed. I also start php artisan queue:work but nothing is done with jobs in database. Any help?
EDIT:
So I just found out that picking jobs from queue is done by SQL selecting the 'default' queue name. But I'm sending mails to queue 'emailsQueue'. So I'm now running Queue Worker like this: php artisan queue:work --queue=emailsQueue and everything's working fine for now. But how can I pick jobs from every queue in database? It's probably not the best attempt, right? It wouldn't make any sense to have named queues, right? But let's say I have one queue for processing register account requests, another queue for changing password requests and so on... So I think it does make sense to process every queue. So how can I do this? Can it be done just by listing the queues like this?
php artisan queue:work --queue=registerAccountEmailQueue,changePasswordEmailQueue...
What exactly does running php artisan queue:work? I thought it's the command to run all queues.
Use queue driver database.
In controller you should write
$this->dispatch(new SendNotifyMail($data));
This will pass your $data to queue. here SendNotifyMail is used as Job Class. So you should also use this in Controller like use App\Jobs\SendNotifyMail;.
Then create a file in Folder Jobs, named SendNotifyMail
<?php
namespace App\Jobs;
use App\Jobs\Job;
use DB;
use Mail;
use Artisan;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendNotifyMail extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
public $timeout = 300; // default is 60sec. You may overwrite like this
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function handle(Mailer $mailer)
{
$data = $this->data; // retrieve your passed data to variable
// your mail code here
}
}
In your command you need to write
php artisan queue:listen
or
php artisan queue:work
Then execute the code.

Laravel Job deserialzation wrong class

I've got a problem while using Laravel's job Deserialization.
This is the Job class that is queued in the database:
class SendRatingEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $order, $user;
public function __construct(User $user, Order $order)
{
$this->order = $order;
$this->user = $user;
}
public function handle()
{
if ($this->order->isRatedByUser($this->user->id)) {
return;
}
Mail::to($this->user->email)->queue(new RatingEmail($this->order, $this->user));
}
}
In the class Order.php, I dispatch this job like this:
class Order {
function queueRating()
{
$when = Carbon::now()->addDays(env('ORDER_DAYS_RATING', 8));
dispatch((new SendRatingEmail($this->buyer, $this))->delay($when));
}
}
So the problem is in the job's handle() function, specifically the error is:
Call to undefined method Illuminate\Database\Query\Builder::isRatedByUser()
It seems as though Laravel gives me the wrong object, instead of App\Order it gives me the QueryBuilder. In the queueRating() function I have checked that the types given in the constructor are the expected types. I have even tested a workaround which also didn't seem to work:
if($this->order instanceof \Illuminate\Database\Query\Builder) {
$this->order = $this->order->first();
}
Also I have looked in the jobs table, and it seems as if the saved models are correct (App\Order)
Edit
Here is the code where the queueRating() function is called. The file is StripeController which handles credit card payments.
public function orderPaid($order) {
$order->payment_done = 1;
$order->save();
$order->chat->open = 1;
$order->chat->save();
$order->queueRating();
}
I found the problem, as always the problem is not where I looked. As it turns out, the code is completely fine, but I forgot to restart the queue worker on the staging server, which meant that changes in the code base were not applied in the queue worker. So the application and queue used different versions.
It is even stated in the Laravel Queue Documentation:
Remember, queue workers are long-lived processes and store the booted application state in memory. As a result, they will not notice changes in your code base after they have been started. So, during your deployment process, be sure to restart your queue workers.
I use the bugtracker Bugsnag, which shows the error and also the line numbers in the code. I had noticed that the line number where the error occurred mismatched the real line number, but couldn't figure out that this was causing the problem.

Translate queued mails (localization)

I am looking for a working solution, to translate queued emails in laravel-5.
Unfortunately, all emails use the default locale (defined under app.locale).
Let's assume, we have two emails in the pipeline, one for an English en user and another for an Japanese jp user.
What data should I pass to the Mail facade to translate (localize) the queued emails?
// User model
$user = User:find(1)->first();
Mailer::queue($email, 'Party at Batman\'s cave (Batcave)', 'emails.party-invitation', [
...
'locale' => $user->getLocale(), // value: "jp", but does not work
'lang' => $user->getLocale(), // value: "jp", but does not work
'language' => $user->getLocale(), // value: "jp", but does not work
]);
I have been struggling to get this done in a more efficient way. Currently I have it set up like this. Hopefully this helps someone in the future with this issue:
// Fetch the locale of the receiver.
$user = Auth::user();
$locale = $user->locale;
Mail::queue('emails.welcome.template', ['user' => $user, 'locale' => $locale], function($mail) use ($user, $locale) {
$mail->to($user->email);
$mail->subject(
trans(
'mails.subject_welcome',
[], null, $locale
)
);
});
And use the following in your template:
{{ trans('mails.welcome', ['name' => ucfirst($user['first_name'])], null, $locale) }}
Note: do not forget to restart your queue
If your emails inherits the built-in Illuminate\Mail\Mailable class you can build your own Mailable class that will take care of translations.
Create your own SendQueuedMailable class that inherits from Illuminate\Mail\SendQueuedMailable.
Class constructor will take current app.location from config and remember it in a property (because laravel queues serializes it).
In queue worker it will take the property back from config and set the setting to the current environment.
<?php
namespace App\Mail;
use Illuminate\Contracts\Mail\Mailer as MailerContract;
use Illuminate\Contracts\Mail\Mailable as MailableContract;
use Illuminate\Mail\SendQueuedMailable as IlluminateSendQueuedMailable;
class SendQueuedMailable extends IlluminateSendQueuedMailable
{
protected $locale;
public function __construct(MailableContract $mailable)
{
parent::__construct($mailable);
$this->locale = config('app.locale');
}
public function handle(MailerContract $mailer)
{
config(['app.locale' => $this->locale]);
app('translator')->setLocale($this->locale);
parent::handle($mailer);
}
}
Then, create your own Mail class that inherits from Illuminate\Mail\Mailable
<?php
namespace App\Mail;
use Illuminate\Contracts\Queue\Factory as Queue;
use Illuminate\Mail\Mailable as IlluminateMailable;
class Mailable extends IlluminateMailable
{
public function queue(Queue $queue)
{
$connection = property_exists($this, 'connection') ? $this->connection : null;
$queueName = property_exists($this, 'queue') ? $this->queue : null;
return $queue->connection($connection)->pushOn(
$queueName ?: null, new SendQueuedMailable($this)
);
}
}
And, voila, all your queued mailables inherited from App\Mailable class automatically will take care of current locale.
In Laravel 5.6 is a locale function added to Mailable:
$infoMail->locale('jp');
Mail::queue($infoMail);
Laravel 5.7.7 introduced the HasLocalePreference interface to solve this issue.
class User extends Model implements HasLocalePreference
{
public function preferredLocale() { return $this->locale; }
}
Now if you use Mail::to() function, Laravel will send with the correct locale.
Also introduced was the Mail chainable locale() function.
Mail::to($address)->locale($locale)->send(new Email());
Here's a solution that worked for me. In my example, I am processing the booking on a queue, using a dedicated Job class.
When you dispatch a job on a queue, pass the locale, you want your email in. For example:
ProcessBooking::dispatch($booking, \App::getLocale());
Then, in your job class (ProcessBooking in my example) store the locale in a property:
protected $booking;
protected $locale;
public function __construct(Booking $booking, $locale)
{
$this->booking = $booking;
$this->locale = $locale;
}
And in your handle method temporarily switch locales:
public function handle()
{
// store the locale the queue is currently running in
$previousLocale = \App::getLocale();
// change the locale to the one you need for job to run in
\App::setLocale($this->locale);
// Do the stuff you need, e.g. send an email
Mail::to($this->booking->customer->email)->send(new NewBooking($this->booking));
// go back to the original locale
\App::setLocale($previousLocale);
}
Why do we need to do this?
Queued emails get the default locale since queue workers are separate Laravel apps. If you ever ran into a problem, when you cleared your cache, but queued 'stuff' (e.g. emails) still showed the old content, it's because of that. Here's an explanation from Laravel docs:
Remember, queue workers are long-lived processes and store the booted application state in memory. As a result, they will not notice changes in your code base after they have been started. So, during your deployment process, be sure to restart your queue workers.
using preferredLocale() is the "official" way to go.
you can implement HasLocalePreference and add method preferredLocale() to the notifiable model (as #Peter-M suggested):
use Illuminate\Contracts\Translation\HasLocalePreference;
class User extends Model implements HasLocalePreference
{
/**
* Get the user's preferred locale.
*
* #return string
*/
public function preferredLocale()
{
return $this->locale;
}
}
and then you just send the notification to that model and locale will be automatically applied to your email template
$user->notify(new InvoicePaid($invoice));
This also works with queued mail notifications.
read more here:
https://floyk.com/en/post/how-to-change-language-for-laravel-email-notifications
I'm faced with the same problem
Without extending the queue class the quickest / dirtiest solution would be to create an email template for each locale.
Then when you create the queue, select the local template i.e.
Mail::queue('emails.'.App::getLocale().'notification', function($message)
{
$message->to($emails)->subject('hello');
});
If the local is set to IT (italian) This will load the view emails/itnotification.blade.php
As I said... Dirty!
Since this method is actually horrible I came across this answer
And its working for me, you actually send the entire translated html version of the email in a variable to the queue and have a blank blade file that echos out the variable when the queue runs.

Categories