I am trying to send an email with a pdf file attached to it.
I checked and the file does exist at the path. I can open it aswell.
I tested if i can download the file with the download function of the storage facade and that also worked.
However when i try it in a queued email, it fails everytime after waiting about 20 seconds.
This is the error i got:
ValueError: fopen(): Argument #1 ($filename) must not contain any null bytes in C:\Users\Gebruiker\PhpstormProjects\FuegoWebsite\vendor\swiftmailer\swiftmailer\lib\classes\Swift\ByteStream\FileByteStream.php:129
Stack trace:
And my email code is:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Storage;
class PdfTestMail extends Mailable
{
use Queueable, SerializesModels;
public $orderId;
public $text;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($orderid, $text)
{
$this->orderId = $orderid;
$this->text = $text;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$file = Storage::disk('private')->get("factuur_order_".$this->orderId.'.pdf');
return $this->text('emails.notifyAdmin')
->subject('Orderbevestiging #'.$this->orderId)
->attach($file, [
'as' => 'factuur.pdf',
'mime' => 'application/pdf'
]);
}
}
I tried to attach the pdf in multiple ways, including the direct output of the barryvdh/dompdf package i use to generate a pdf.
nothing works and i have no idea why.
The attach method takes a filename for the file to attach, not data. You are probably looking for the attachData method:
public function attachData($data, $name, array $options = [])
To switch to attachData:
->attachData($file, 'factuur.pdf', ['mime' => 'application/pdf'])
Laravel 8.x Docs - Mail - Attachments - Raw Data Attachments attachData
Related
I am taking over a project and a client wants me to add a mailing feature like a gmail with inbox, sent messages and the user can also reply to emails from outside the system and the people receiving it can also reply. I have basic knowledge on laravel but this is my first time creating a feature like this can anyone give a stepping stone. This project has a VUE.js as a front end and a laravel as an API
Step -1: Create a Mail using,
php artisan make:mail MailName
Check the file MailName.php inside App/Mail/ directory
Step - 2: Create a function to send the email with the requested data.
public function sendMail(Request $request){
$data = [
'value1'=> $request->value1,
'value2'=> $request->value2,
'value3'=> $request->value3
];
//You can add any function like storing the values into db.
if(someconditions){
\Mail::to('emailid#domainname.com')->send(new MailName($data));
return back()->with('success', 'Email has been sent successfully!');
}
else
{
return back()->with('error', 'Something went wrong!');
}
}
Step - 3: Open the App/Mail/MailName.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Address;
//You can include your model here.
class DeleteResponseMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public $data;
public function __construct($data)
{
$this->data = $data;
}
/**
* Get the message envelope.
*
* #return \Illuminate\Mail\Mailables\Envelope
*/
public function envelope()
{
return new Envelope(
from: new Address('fromemail#somedomain.com', 'From Email Name'),
replyTo: [
new Address('admin#yourdomain.com', 'Your email name'),
],
subject: 'Email Subject',
);
}
/**
* Get the message content definition.
*
* #return \Illuminate\Mail\Mailables\Content
*/
public function content()
{
return new Content(
view: 'emails.mail',
);
}
/**
* Get the attachments for the message.
*
* #return array
*/
public function attachments()
{
return [];
}
}
Step - 4: Create mail.blade.php under resources/views/emails
Hi $data['value1'],
We have received your application. Thanks!
Step - 5: Open your .env file and add the email configurations.
MAIL_MAILER=enteryourmailerhere (ex.smtp)
MAIL_HOST=enteryourmailhosthere (ex.smtp.gmail.com)
MAIL_PORT=entertheport (ex. 25)
MAIL_USERNAME=emailid#yourdomain.com
MAIL_PASSWORD=emailpassword
MAIL_ENCRYPTION=""
MAIL_FROM_ADDRESS="fromemail#yourdomain.com"
MAIL_FROM_NAME="${APP_NAME}"
That's all! It should work.
I have some notifications (that get sent out to SendGrid) and I am using table queues.
I build my notification as below
$data = [
'email_message' => $message,
'subject' => $request->subject,
'email' => $request->email
];
$user->notify(new EmailGeneralNotification($data));
My Notification looks like this;
<?php
namespace App\Notifications;
use Illuminate\Notifications\Notification;
use NotificationChannels\SendGrid\SendGridChannel;
use NotificationChannels\SendGrid\SendGridMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
// use Illuminate\Queue\SerializesModels;
use App\Models\NotificationTemplate;
class EmailGeneralNotification extends Notification implements ShouldQueue
{
use Queueable;
// use SerializesModels;
protected $data;
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct(array $data) {
$this->data = $data;
}
public function via($notifiable)
{
return [
SendGridChannel::class,
];
}
public function toSendGrid($notifiable)
{
$reference = 'internal_general_email';
$template = NotificationTemplate::where('reference',$reference)->latest('updated_at')->first();
if(!$template)
{
dd("No template was found for " . $reference);
}
return (new SendGridMessage($template->template_id))
/**
* optionally set the from address.
* by default this comes from config/mail.from
* ->from('no-reply#test.com', 'App name')
*/
/**
* optionally set the recipient.
* by default it's $notifiable->email:
* ->to('hello#example.com', 'Mr. Smith')
*/
->payload([
"subject" => (array_key_exists('subject', $this->data)) ? $this->data['subject'] : '',
"email_message" => (array_key_exists('email_message', $this->data)) ? $this->data['email_message'] : '',
]);
}
}
This gets sent via a SendGrid notification type.
The funny this is that when I remove the implements ShouldQueue and use Queueable and run my function everything works fine.
When I enable the queue I get the below;
[2022-10-30 08:34:08] local.ERROR: Declaration of Ramsey\Uuid\Uuid::unserialize(string $data): void must be compatible with Serializable::unserialize($serialized) {"exception":"[object] (Symfony\\Component\\ErrorHandler\\Error\\FatalError(code: 0): Declaration of Ramsey\\Uuid\\Uuid::unserialize(string $data): void must be compatible with Serializable::unserialize($serialized) at /var/www/html/vendor/ramsey/uuid/src/Uuid.php:307)
[stacktrace]
#0 {main}
"}
I have updated php to v8 and got Laravel to 8.83.25 but still no joy
I get the following error message out of the blue.
InvalidArgumentException : View [themes.] not found.
Exception trace:
1 Illuminate\View\FileViewFinder::findInPaths("themes.")
C:\xampp\htdocs\RoosterIKEA\vendor\laravel\framework\src\Illuminate\View\FileViewFinder.php:92
2 Illuminate\View\FileViewFinder::findNamespacedView("mail::themes.")
C:\xampp\htdocs\RoosterIKEA\vendor\laravel\framework\src\Illuminate\View\FileViewFinder.php:76
Any idea which file this could be and what is going on?
In the class Illuminate\Mail\Markdown line 64
return new HtmlString(($inliner ?: new CssToInlineStyles)->convert(
$contents, $this->view->make('mail::themes.'.$this->theme)->render()
));
It appears that $this->theme is an empty string
Now the class already defines the property on line 24
/**
* The current theme being used when generating emails.
*
* #var string
*/
protected $theme = 'default';
Which means that you may have overridden this property by an empty string or maybe a null value in your markdown mailable
If you publish the components by
php artisan vendor:publish --tag=laravel-mail
You'll see a CSS file in resources/views/vendor/mail/html/themes named default.css
I found a way to reproduce this error on purpose to have a Mailable class like so
Run
php artisan make:mail OrderShipped --markdown=emails.orders.shipped
Then override the theme property by an empty string
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class OrderShipped extends Mailable
{
use Queueable, SerializesModels;
protected $theme = ''; // <--- HERE
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->markdown('emails.orders.shipped');
}
}
Now send an email
use App\User;
use App\Mail\OrderShipped;
Route::get('/', function () {
\Mail::to(User::first())->send(new OrderShipped());
});
And you would get the same error
The solution here is to either remove the protected $theme = ''; property or set it to default
I hope this helps
I want to send many emails.
Currently I write basic code use PHPMailler to send mail using queue. It works, but everytime new queue is run, it have to connect to SMTP again, so i get bad perfomance.
I find SMTPKeepAlive property on PHPMailler documentation:
$phpMailer = New PHPMailer();
$phpMailer->SMTPKeepAlive = true;
Is it imposible and how to keep $phpMailler object for next queue? So PHPMailler have not to connect again by using previous connection.
If you are using Laravel then you have to use Laravel's feature inbuilt.
Please find below documents:
https://laravel.com/docs/5.6/mail
Please find a piece of code for send mail and adding in a queue:
use App\Mail\EmailVerifyMail;
\Mail::queue(new EmailVerifyMail($users));
EmailVerifyMail.php
<?php
namespace App\Mail;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class EmailVerifyMail extends Mailable
{
use Queueable, SerializesModels;
public $user;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$this->to($this->user)->subject(__('mail.subjects.verification_link', ['USERNAME' => $this->user->name]));
return $this->view('mails/emailVerify', ['user' => $this->user]);
}
}
I need to generate a pdf of user with their related data. I have created a function in controller to generate pdf(using snappy-pdf package). Data is loaded properly, pdf generated and uploaded on S3 successfully. Now I am trying to put that function in queue with user data. But data is not loaded for generation pdf. Instead it only laod the user model data but not the related data (like property and contact).
$user = User::where("id", 83)->with(['property', 'contacts'])->first();
$contact_list = [];
if(count($user->contacts)){
foreach ($user->contacts as $contact) {
array_push($contact_list, $contact->contactDetails->name);
}
}
$user->contact_list = $contact_list;
return view('pdf_view', ["user" => $user]);
if($request->has('download')) {
// dispatch the event
/*$pdf = PDF::loadView('pdf_view', ["user" => $user]);
// upload pdf
Storage::disk('s3')->put("user-invoice/user.pdf", $pdf->output(), 'public');*/
dispatch(new GenerateSignedDocument($user));
return "done";
}
And here is my job file.
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Log;
use PDF;
use Storage;
class GenerateSignedDocument implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*
* #return void
*/
private $user;
public function __construct($user)
{
$this->user = $user;
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
// Log::info('Showing user: '.$this->user);
$pdf = PDF::loadView('pdf_view', ["user" => $this->user]);
// upload pdf
Storage::disk('s3')->put("user-invoice/user.pdf", $pdf->output(), 'public');
}
}
The SerializesModels trait only stores the model's key and reloads the model from the database when it is unserialized, so this is likely not going to load your relationships.
This is usually a big benefit as you'll receive fresh data when your queued job actually runs, not the data from when the job was scheduled.
In any case, it's probably best to either:
Load the relationships the job requires in the job itself and not rely on them being preloaded.
or
Pass the property and contacts as separate dependencies to your job class.
I don't know this is the perfect solution or not but I do this and work fine.
I called the required function(uploadUserDocument) for performing pdf generation in the handler function.
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
app('App\Http\Controllers\UserController')->uploadUserDocument($this->user_id);
}
and dispatch the event as it is in the controller
dispatch(new GenerateSignedDocument(83));
and put the required code for generating pdf in the uploadUserDocument function like this.
/**
* Function is used to Save document to s3 cloud
* #param $user_id
* #return Response
*/
public function uploadUserDocument($user_id){
$pdf = PDF::loadView('pdf_view', ["user" => $user]);
// upload pdf
Storage::disk('s3')->put("user-invoice/user.pdf", $pdf->output(), 'public');
}