Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

mprythero's avatar

Attaching Generated PDF to Mailable (Laravel Snappy)

Hi All -

I'm stuck on an issue that I believe should be readily fixable, I'm just doing it wrong at the moment.

I have a mailable that I would like to attach a generated pdf to, but the problem is somewhere I am not making the appropriate connection.

At this time, my mailable is defined as such:

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use PDF;

use App\Shipment;

class newFreightBill extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public $shipment;
    
    public function __construct(Shipment $shipment)
    {
        $this->shipment = $shipment;
        $this->attachmentURL = $shipment->url_string;
        $this->proNumber = $shipment->pro_number;
        
        $shipment_details = $shipment->shipment_details;
 
        $this->pdf = PDF::loadFile('shipments.pdf', compact('shipment','shipment_details'))
                        ->setOption('images', true)
                        ->setOption('enable-javascript', true)
                        ->setOption('javascript-delay', 100);
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from('[email protected]')
                    ->subject('New Freight Bill Created - '. $this->proNumber)
                    ->view('emails.shipments.created')
                    ->attach($this->pdf, [
                            'as' => 'freightbill.pdf', 
                            'mime' => 'application/pdf',
                    ]);
    }
}

I currently use Laravel Snappy to generate pdfs solely because it plays nice with some javascript I have.

The problem is I always get the error: basename() expects parameter 1 to be string, object given

which I believe to be in relation to the line ->attach($this->pdf....

If anyone can help or has any suggestions, I'd be incredibly grateful.

Thanks! Matthew

0 likes
10 replies
Talinon's avatar

I believe you're getting that error is because ->attach() is looking for a string (basename path) to the file. You are passing in $this->pdf, which is the Laravel Snappy object.

I think what you need to do is first export the pdf to a temporary file, and then pass the path to that file to attach()

Something like:

$this->path = 'some/temporary/location/freightbill.pdf';

$this->pdf = PDF::loadFile('shipments.pdf', compact('shipment','shipment_details'))
                        ->setOption('images', true)
                        ->setOption('enable-javascript', true)
                        ->setOption('javascript-delay', 100)
            ->save($this->path);

then:


    return $this->from('[email protected]')
                    ->subject('New Freight Bill Created - '. $this->proNumber)
                    ->view('emails.shipments.created')
                    ->attach($this->path, [
                            'as' => 'freightbill.pdf', 
                            'mime' => 'application/pdf',
                    ]);

You would need to take care of some garbage collection to remove the temporary file after the email has been delivered.

mprythero's avatar

@Talinon:

At the moment I get the following error:

"message": "The exit status code '1' says something went wrong:\nstderr: \"Loading pages (1/6)\r\n[>                                                           ] 0%\r[======>                                                     ] 10%\rError: Failed loading page http://shipments.pdf (sometimes it will work just to ignore this error with --load-error-handling ignore)\r\nExit with code 1 due to network error: HostNotFoundError\r\n\"\nstdout: \"\"\ncommand: \"C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf\" --lowquality --images --enable-javascript --javascript-delay \"100\" \"shipments.pdf\" \"temp/freightbill.pdf\".",

At the moment my mailable looks like this:

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use PDF;

use App\Shipment;

class newFreightBill extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public $shipment;
    
    public function __construct(Shipment $shipment)
    {
        $this->shipment = $shipment;
        $this->attachmentURL = $shipment->url_string;
        $this->proNumber = $shipment->pro_number;
        
        $shipment_details = $shipment->shipment_details;
        
        $this->path = 'temp/freightbill.pdf';
 
        $this->pdf = PDF::loadFile('shipments.pdf', compact('shipment','shipment_details'))
                        ->setOption('images', true)
                        ->setOption('enable-javascript', true)
                        ->setOption('javascript-delay', 100)
            ->save($this->path);
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from('[email protected]')
                    ->subject('New Freight Bill Created - '. $this->proNumber)
                    ->view('emails.shipments.created')
                    ->attach($this->path, [
                            'as' => 'freightbill.pdf', 
                            'mime' => 'application/pdf',
                    ]);
    }
}

I am able to use the laravel pdf generator for other aspects, so I know it can produce it, but somewhere in the current code, it's seeing an error. Do you happen to see anything or have anything to suggest? I'm going to keep up with what you suggested and maybe the right combination will get this to work.

Thanks for the help! Matt

Talinon's avatar

If you're trying to generate a PDF, you need to change your loadFile() call to accept a route. As of right now, you are just providing it a file name, so it's trying to connect to "http://shipment.pdf", which obviously doesn't exist.

Instead, you should provide the url to be loaded that Snappy / wkhtmltopdf will generate a PDF based upon.

$this->pdf = PDF::loadFile(url('shipments/generate', [$this->proNumber]))....

This way it'll request http://your-host/shipments/generate/<pro-number> .. or something along these lines.

mprythero's avatar

So looking at this:

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use PDF;

use App\Shipment;

class newFreightBill extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public $shipment;
    
    public function __construct(Shipment $shipment)
    {
        $this->shipment = $shipment;
        $this->attachmentURL = $shipment->url_string;
        $this->proNumber = $shipment->pro_number;
        
        $shipment_details = $shipment->shipment_details;
        
        $this->path = 'temp/freightbill.pdf';
 
        $this->pdf = PDF::loadFile(url('/shipments/download/', [$this->attachmentURL]))
            ->save($this->path);
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from('[email protected]')
                    ->subject('New Freight Bill Created - '. $this->proNumber)
                    ->view('emails.shipments.created')
                    ->attach($this->path, [
                            'as' => 'freightbill.pdf', 
                            'mime' => 'application/pdf',
                    ]);
    }
}

does it look like I have everything? Because for some reason I keep getting "Sorry this page can't be found" in a generated pdf. And it does appear in the temp folder, but I feel like I messed something up in the loadFile url.

At the moment, the url should be going to this controller function:

  public function downloadPDF(Shipment $shipment){
            $shipment_details = $shipment->shipment_details;
 
            $pdf = PDF::loadView('shipments.pdf', compact('shipment','shipment_details'))
                    ->setOption('images', true)
                    ->setOption('enable-javascript', true)
                    ->setOption('javascript-delay', 10);
            return $pdf->download('shipment'.$shipment->url_string.'.pdf');
        }

thanks again for everything! Matt

Talinon's avatar

The download() method will force the client's browser to download the file, so you don't want your generator endpoint to use that. You also are making the same error with loadView() as you did with loadFile() - both of these should be URLs to a webpage.

What you need to do is to create a GET route, and format your shipment document the way you want. You can use css, javascript, whatever. Once you're happy with the way it looks in your browser, pass that route to your loadFile() or loadView() methods within your Mailable class. Laravel Snappy will dispatch wkhtmltopdf to send a GET request to your route, parse all the data, and generate a PDF file on your server's file system. This file will then be available to your Mailable class to send the file as an email attachment.

So, in your Mailable class, change the loadFile() call to a GET route. Within the controller load a view like normal -- no need to run any Laravel Snappy facades there at all.

Controller:

public function show(Shipment $shipment){
            $shipment_details = $shipment->shipment_details;

        return view('shipments.show', compact('shipment', 'shipment_details')); 

Mailable (newFreightBill):

        
        $this->path = 'temp/freightbill.pdf';
 
        $this->pdf = PDF::loadFile(url('/shipments/show/', [$shipment->pro_number]))
            ->save($this->path);
    }

mprythero's avatar

Alright, I understand what you're saying, so I'm sorry I've been a bit daft at this haha.

Anyways, my new mailable looks like this:

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use PDF;

use App\Shipment;

class newFreightBill extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public $shipment;
    
    public function __construct(Shipment $shipment)
    {
        $this->shipment = $shipment;
        $this->attachmentURL = $shipment->url_string;
        
        $shipment_details = $shipment->shipment_details;
        
        $this->path = 'temp/freightbill.pdf';
 
        $this->pdf = PDF::loadFile(url('shipments/pdf/', [$this->attachmentURL]))
            ->save($this->path);
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->from('[email protected]')
                    ->subject('New Freight Bill Created - '. $this->proNumber)
                    ->view('emails.shipments.created')
                    ->attach($this->path, [
                            'as' => 'freightbill.pdf', 
                            'mime' => 'application/pdf',
                    ]);
    }
}

With the above [$this->attachmentURL], I also tried your suggestion in the format of [$shipment->url_string], but for the life of me I continue to get "Sorry, the page you are looking for could not be found." in a pdf in the temp folder. It's still not sending through either but I assume it's been held up at this junction.

And here is the controller that - shipments/pdf/{shipment} points to:

public function showPDF (Shipment $shipment){
            $shipment_details = $shipment->shipment_details;
 
            return view('shipments.pdf', compact('shipment', 'shipment_details')); 
}

And if I go to the link above using an example, my pdf template loads fine in the browser, so I assume it must be originating in the loadFile(url... point.

PS - In the model, I have defined the shipment model to point to the field "url_string" just to make it a little more difficult to just look up another record rather than just 1,2,3. And that works fine, I just thought I'd point it out to you rather than the pro_number field I must've accidentally left in my previous posts.

Thanks again for everything - Matt

Talinon's avatar

Oh.. so, $shipment->url_string returns a string such as: http://cmxtrucking.com/shipments/show/{id} ?

if that is the case, just reduce your call to:

$this->pdf = PDF::loadFile($shipment->url_string)
            ->save($this->path);

Or, just hard-code it until you get it working, then refactor:

$this->pdf = PDF::loadFile('http://cmxtrucking.com/shipments/show/1')
            ->save($this->path);
Talinon's avatar

It also looks like loadFile() only takes 1 argument, where loadView() accepts an additional argument for view data.. which makes sense. This could be another reason why you were getting the 'page not found' error as it would of only been hitting "/shipments/show"

mprythero's avatar

Well it looks like I finally managed (with all of your help, which I appreciate because I might've gone insane) to get this working with the following code:

    public function __construct(Shipment $shipment)
    {
        $this->shipment = $shipment;
        $this->attachmentURL = $shipment->url_string;
        $shipment_details = $shipment->shipment_details;
        $this->proNumber = $shipment->pro_number;
        $this->path = 'temp/freightbill.pdf';
 
        $this->pdf = PDF::loadView('shipments.pdf', compact('shipment', 'shipment_details'))
            ->save($this->path);
    }

I was curious though, how would you suggest I go about deleting the temp file? I will look into it, but I'm hoping you might have a hint to point me in the right direction.

Thanks again for everything!!

Matt

Talinon's avatar

First, I would suggest making the temporary PDF filename unique. This could be as simple as adding a hash or including the id in the filename:

$this->path = 'temp/freightbill_' . $shipment->pro_number . '.pdf';

This way, multiple simultaneous (or near simultaneous) requests won't overwrite your file.

As for cleaning up, there are a few ways I can think of going about it. One way would be just to have a scheduled task (cron job) that executes a script that clears out the temporary directory each night.

Another option would be to run a delayed process that deletes the file after so much time has past. You could harness Symphony's Process Component to execute a script that could delete the file.

http://symfony.com/doc/current/components/process.html

Or, you could create a delayed Job that could be responsible for deleting the file:

https://laravel.com/docs/5.5/queues#delayed-dispatching

Please or to participate in this conversation.