Oh Dear is the all-in-one monitoring tool for your entire website. We monitor uptime, SSL certificates, broken links, scheduled tasks and more. You'll get a notifications for us when something's wrong. All that paired with a developer friendly API and kick-ass documentation. O, and you'll also be able to create a public status page under a minute. Start monitoring using our free trial now.

Using the `Attachable` interface to attach any kind of object to a mail in a Laravel app

Original – by Freek Van der Herten – 4 minute read

Laravel 9 has gained a excellent new way to attach files in mails. Using the Attachable interface, you can specify what should happen when an object gets used as an attachment.

Using our media library package you can easily associate any file with an Eloquent model. We've added support for Laravel's Attachable in the latest version of the package.

In this blog post, I'd like to tell you all about it.

Making use of Attachable

Let's look at how you would attach files to Mailables in previous versions of Laravel.

Imagine that your app has a simple Photo model with a path property that contains the path to the actual image file. Here is how you could attach the image file in a Mailable.

// in a Mailable
public function __construct(public Photo $photo)
{
}

public function build()
{
    return $this
       ->view('emails.yourView')
       ->attach($this->photo->path);
}

This is not too bad, but it is strange that the mailable should know that Photo has a path property.

In Laravel 9, a new Attachable interface was introduced. Let's use it in our Photo model.

namespace App\Models;
 
use Illuminate\Contracts\Mail\Attachable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Mail\Attachment;
 
class Photo extends Model implements Attachable
{
	// ...
		
	public function toMailAttachment(): Attachment
	{
	    return Attachment::fromPath($this->path);
	}
}

With this in place, you can pass a Photo model to attach.

// in a Mailable
public function __construct(public Photo $photo)
{
}

public function build()
{
    return $this
       ->view('emails.orders.shipped')
       ->attach($this->photo);
}

In this case, we only gained that we didn't have to specify ->path. In real-world apps, determining the right path could be more complex, and you could, for instance, be using different disks.

Attaching media

Laravel Media Library is our powerhouse package to associate files with Eloquent models. If you aren't familiar with it yet, I can highly encourage going through the docs or watching the free video course.

Imagine that your app has a blog post model that you have prepared to use with media library .

Let's associate an image file to the model and use s3 to store it.

BlogPost::find($blogPostId);
   ->addMedia($pathToImage)
   ->toMediaCollection('images', 's3');

Behind the scenes, the media library will create a Media model that holds all the information regarding the image file (the name, the path, the disk name, the mime type, ...) and a reference to the BlogPost model.

Let's now attach that image file. Using getFirstMedia we can get the first Mediamodel that is associated with the BlogPost model.

// in a Mailable
public function __construct(public BlogPost $blogPost)
{
}

public function build()
{
    return $this
       ->view('emails.yourView')
       ->attach($this->blogPost->getFirstMedia());
}

And that's all you need to do to attach that first image. Your mailable is oblivious to the fact that the image is stored on S3.

Let's look at how we use Laravel's attachable Attachable interface in Media Library's Media model. Here's the implementation of the toMailAttachment attachment that the interface requires. As you can see, we use the properties of the Media model to configure the returned Attachment.

namespace Spatie\MediaLibrary\MediaCollections\Models;

use Illuminate\Contracts\Mail\Attachable;

class Media extends Model implements Attachable
{
		// all other methods...

    public function toMailAttachment(): Attachment
		{
        $path = $this->getUrlGenerator($conversion)->getPathRelativeToRoot();

        $attachment = Attachment::fromStorageDisk($this->disk, $path)->as($this->file_name);

        if ($this->mime_type) {
            $attachment->withMime($this->mime);
        }

        return $attachment;
    }
}

In closing

Tim McDonald did a very nice job adding Attachable to Laravel. In this blog post, we've used the Attachableinterface on models. But you can add this to any object. Head over to the Laravel docs to know more. There are a few options that we didn't touch on in this blog post.

Media Library can also create thumbnails and any conversion for your files. These converted files can also be used as attachments. Head to the Media Library docs to know more about that.

Be sure also to look at this extensive list of other packages that our team has made. I'm pretty sure there's something there for your next project.

Stay up to date with all things Laravel, PHP, and JavaScript.

You can follow me on these platforms:

On all these platforms, regularly share programming tips, and what I myself have learned in ongoing projects.

Every month I send out a newsletter containing lots of interesting stuff for the modern PHP developer.

Expect quick tips & tricks, interesting tutorials, opinions and packages. Because I work with Laravel every day there is an emphasis on that framework.

Rest assured that I will only use your email address to send you the newsletter and will not use it for any other purposes.

Comments

Comments powered by Laravel Comments
Want to join the conversation? Log in or create an account to post a comment.