skip to content

In Laravel, what do a mailable's envelope(), content() and attachments() methods define, and how do you send that mailable to a customer?

level: juniorimportance: must knowfreq 62%

answer

  1. one class per kind of email
  2. php artisan make:mail OrderConfirmed
  3. Envelope: subject, from, replyTo, tags
  4. Content: view, markdown, text, with
  5. public properties reach the view

basics

~10 s

A mailable, generated by make:mail, splits one email into envelope() for subject and sender metadata, content() for the Blade or Markdown view, and attachments() for files; Mail::to($customer)->send(new OrderConfirmed($order)) delivers it.

solid answer

~40 s

`php artisan make:mail OrderConfirmed` creates a class in `app/Mail` extending `Illuminate\Mail\Mailable` with three methods. `envelope()` returns an `Envelope` carrying the subject, `from`, `replyTo`, `cc`/`bcc`, tags and metadata. `content()` returns a `Content` naming a Blade `view`, a `markdown` template or a plain `text` view, plus optional `with` data; every **public property** declared on the mailable is also passed to the view automatically. `attachments()` returns an array of `Attachment` objects, such as the order's PDF receipt. You send it through the `Mail` facade: `Mail::to($order->customer)->send(new OrderConfirmed($order))`, chaining `cc()` or `bcc()` when needed. Passing a model to `to()` uses its `email` and `name` attributes, and when the envelope sets no sender the global `mail.from` address (`MAIL_FROM_ADDRESS`) is used.

code

php · 29 lines
php
<?php

namespace App\Mail;

use App\Models\Order;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Attachment;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;

class OrderConfirmed extends Mailable
{
    public function __construct(public Order $order) {}

    public function envelope(): Envelope
    {
        return new Envelope(subject: "Order #{$this->order->number} confirmed");
    }

    public function content(): Content
    {
        return new Content(markdown: 'mail.orders.confirmed');
    }

    public function attachments(): array
    {
        return [Attachment::fromStorage("receipts/{$this->order->id}.pdf")->as('receipt.pdf')];
    }
}

go deeper

for a junior

Recall the three methods and what each returns, the make:mail command, and the Mail::to()->send() chain. Mention that public properties reach the view.

for a middle

Explain how view data is gathered from public properties and with, how the global from address fills in, and why to() appending makes loop reuse dangerous.

for a senior

Show you keep mailables thin and previewable: data prepared in the constructor, a local preview route, one instance per recipient, and a deliberate choice between sending now and queueing.

for a principal

Frame mailables as the team's email contract: a class per message type makes copy review, localisation and provider changes cheap, while ad-hoc Mail::send calls scatter that knowledge.

## What a mailable is A **mailable** is a plain PHP class that describes one kind of email your application sends — "order confirmed", "invoice overdue", "shipment dispatched". It extends `Illuminate\Mail\Mailable`, lives in `app/Mail` by convention, and keeps everything about that email in one place: who it is from, what it says, which template renders it and which files ride along. The controller or job that sends it only decides *who* receives it and *when*. You generate one with Artisan: ```bash php artisan make:mail OrderConfirmed php artisan make:mail OrderConfirmed --markdown=mail.orders.confirmed ``` The first form writes a class whose `content()` points at a placeholder view; the second also writes a Markdown template and points `content()` at it. The generated class already uses the `Queueable` and `SerializesModels` traits, so it can be queued later without changes. ## The three methods | Method | Returns | What it defines | |---|---|---| | `envelope()` | `Illuminate\Mail\Mailables\Envelope` | subject, `from`, `replyTo`, `to`/`cc`/`bcc`, tags, metadata | | `content()` | `Illuminate\Mail\Mailables\Content` | `view` (HTML Blade view), `text` (plain-text view), `markdown` template, `htmlString`, `with` data | | `attachments()` | `array` of `Attachment` (or `Attachable` objects) | files attached to the message | An optional fourth method, `headers()`, returns a `Headers` object for a custom `Message-ID`, `References` or extra text headers. Named arguments keep these readable: `new Envelope(subject: 'Order #1042 confirmed')` and `new Content(view: 'mail.orders.confirmed')`. ## The envelope in more detail The `Envelope` is where message-level metadata lives, separate from the body: - `from:` accepts a string or an `Illuminate\Mail\Mailables\Address`, which carries a display name: `new Address('[email protected]', 'Shop Orders')`. - `replyTo:` sends customer replies to a support inbox instead of a no-reply sender. - `tags:` and `metadata:` are passed to providers that support them, so you can filter "order-confirmation" messages in the provider's dashboard or webhooks. - `to:`, `cc:` and `bcc:` can be fixed here, though recipients usually come from the call site. Keeping the subject in `envelope()` rather than in the controller means every place that sends the mailable gets the same, reviewable subject line — and it can use the mailable's own data, such as the order number. ## Getting data into the template There are two routes, and interviewers like to hear both: - **Public properties.** Every initialized public property declared on your mailable class is handed to the view under its own name. With constructor promotion, `public function __construct(public Order $order) {}` is enough for `{{ $order->number }}` to work in the template. - **The `with` argument.** `new Content(view: 'mail.orders.confirmed', with: ['total' => $this->order->formattedTotal()])` passes computed values; the property itself can stay `protected` if you do not want it exposed wholesale. The sender works the same way: if `envelope()` sets no `from`, Laravel falls back to the global `from` address in `config/mail.php`, read from `MAIL_FROM_ADDRESS` and `MAIL_FROM_NAME`. ## Sending it 1. Pick recipients with the `Mail` facade: `Mail::to($address)`. It accepts a string, an array, a model or a collection; for objects Laravel reads the `email` and `name` attributes. 2. Chain optional extras: `->cc($manager)`, `->bcc($archive)`. 3. Hand over the mailable: `->send(new OrderConfirmed($order))`. `send()` renders the view and passes the message to the configured mailer during the current request, returning an `Illuminate\Mail\SentMessage` (or `null` if a listener cancelled it). The same pending mail also offers `queue()` and `later()` for background delivery. ## A trap worth naming `to()`, `cc()` and `bcc()` **append** to the mailable's recipient lists rather than replacing them. If you build one mailable instance and reuse it in a loop — `foreach ($emails as $e) { Mail::to($e)->send($mail); }` — the second send goes to two people, the third to three, and so on. Create a fresh mailable inside the loop. ## Checking your work Because `Mailable` implements `Renderable`, you can return one from a local-only route (`return new OrderConfirmed($order);`) to see the HTML in a browser, or call `->render()` to get the string. Nothing is sent when you do this, which makes it the quickest way to iterate on layout before touching a transport. ## Summary - `make:mail` scaffolds the class; `envelope()`, `content()` and `attachments()` split metadata, body and files. - Public properties and `with` data feed the view. - `Mail::to(...)->send(...)` delivers now; the global `from` fills in a missing sender. - Never reuse one mailable instance across recipients in a loop.

  • What goes wrong if you create one mailable instance before a loop and call Mail::to($email)->send($mail) for each address?
    `to()` appends addresses to the mailable's recipient list instead of replacing it, so every send also goes to all earlier recipients: the third customer's email is delivered to three people, which also leaks addresses. Create a new mailable inside the loop, one per recipient.
  • How can you preview a mailable's HTML without sending anything?
    A mailable implements `Renderable`, so return it from a route (`return new OrderConfirmed($order);`), usually one registered only locally, and the browser shows the rendered HTML. Calling `->render()` returns the same HTML as a string. No mailer or transport is involved.
  • Where does the From address come from when envelope() does not set one?
    From the global `from` entry in `config/mail.php`, which reads `MAIL_FROM_ADDRESS` and `MAIL_FROM_NAME` (the name defaults to `APP_NAME`). Setting `from:` on the `Envelope` overrides it for that mailable only.

saying these in an interview costs you the question

  • The subject of a mailable is set in content() next to the view name.
  • Only data passed through Content's with argument is visible in the view.
  • Reusing one mailable instance in a loop sends each recipient exactly one email.
  • An envelope without a from address sends the email with no sender at all.
  • Mail::to() only accepts a plain email string, never a model.