Developer Guides

Laravel Mail::raw — when to use it (and when not)

Working Laravel Mail::raw and Mail::html examples, the difference between plain text and raw MIME, and when to move production email into queued mailables.

Published Last updated
ArawaMail developer guide graphic: Laravel Mail::raw — when to use it and when not

Laravel Mail::raw() does one narrow job: it sends a message with a plain-text body. That makes it useful for smoke tests, internal alerts, and genuinely simple one-off messages. It does not mean “send this complete raw MIME document.”

That distinction is where most examples go wrong. A copied MIME payload, an inbound webhook body, and a plain string are three different things. Laravel's current mailer contract describes raw() as sending “only a raw text part”; use it when that is exactly what you need, then move production message types into mailables and a queue.

A working Laravel Mail::raw example

<?php

use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Mail;

Mail::raw(
    'The nightly import completed with 14 skipped rows.',
    function (Message $message): void {
        $message
            ->to('[email protected]')
            ->subject('Nightly import report');
    }
);

This creates a normal email with a single plain-text part. Laravel still uses the mailer configured in config/mail.php; raw describes the content, not the transport. It does not bypass SMTP or your email API, and it does not bypass domain authentication.

Use Mail::html when the body is already HTML

Laravel also provides Mail::html() for a one-off HTML body:

<?php

use Illuminate\Mail\Message;
use Illuminate\Support\Facades\Mail;

Mail::html(
    '<p>Your export is ready.</p>' .
    '<p><a href="https://example.com/exports/42">Download export</a></p>',
    function (Message $message): void {
        $message
            ->to('[email protected]')
            ->subject('Your export is ready');
    }
);

This is appropriate for a controlled, static fragment. It becomes fragile once the message needs reusable layout, localization, a plain-text alternative, attachments, previewing, or tests. Never concatenate untrusted user input into the HTML string. Render escaped data through a Blade view or mailable instead.

Choose the smallest tool that still fits the message

NeedUseWhy
Plain internal alert or smoke testMail::raw()No template is needed; the content is intentionally text-only.
Controlled one-off HTML fragmentMail::html()Fast, but still HTML-only and easy to outgrow.
Receipt, reset, invite, notification, or customer workflowMailable plus queueReusable content, HTML and text parts, testing, retries, and clear ownership.
Existing RFC 5322/MIME document that must remain intactA raw-message provider endpoint or deliberate Symfony Mime handlingMail::raw() would send the MIME source as visible plain text.

Raw text is not raw MIME

A raw MIME message includes headers, boundaries, encodings, and multiple body parts. It may begin like this:

From: [email protected]
To: [email protected]
Subject: Invoice 1842
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="invoice-1842"

--invoice-1842
Content-Type: text/plain; charset=UTF-8

Invoice 1842 is ready.
--invoice-1842
Content-Type: text/html; charset=UTF-8

<p>Invoice 1842 is ready.</p>
--invoice-1842--

If you pass that string to Mail::raw(), the recipient can receive the boundary markers and headers as message text. Laravel is not being asked to parse the document; it is being asked to create a plain-text part containing those characters.

Preserving raw MIME is justified in narrower workflows:

  • Archiving the exact payload received by an inbound-email webhook.
  • Relaying or migrating messages where existing headers and multipart boundaries must survive.
  • Processing signed or encrypted mail where reconstructing the body may invalidate a signature.
  • Testing a provider's raw-message ingestion endpoint with a known fixture.

Even there, storing, parsing, and sending are separate decisions. Inbound messages may contain personal data, attachments, authentication results, and malicious HTML. Do not dump complete MIME payloads into ordinary application logs. Parse them in an isolated path, validate attachment limits, and retain only what the workflow needs.

For production mail, give the message a class

A password reset is not “some text.” It is a product event with a subject, recipient, expiry time, URL, localization rules, and delivery history. A mailable makes that contract visible.

php artisan make:mail WorkspaceInvite --markdown=mail.workspace.invite

Then queue the mailable rather than waiting for the network inside the web request:

<?php

use App\Mail\WorkspaceInvite;
use Illuminate\Support\Facades\Mail;

Mail::to($user)->queue(
    new WorkspaceInvite($workspace)
);

Laravel's mail documentation recommends queues because sending can slow the request. A mailable may also implement ShouldQueue so calls to send() are queued automatically.

If the message is created inside a database transaction, dispatch it after the transaction commits. Otherwise a fast worker may try to render a model before its row exists. Laravel supports afterCommit() for this boundary.

What the queue does—and does not—solve

A queue moves the network call out of the request and gives you controlled retries. It does not make duplicate sending impossible. If a provider accepts the message and the worker crashes before recording success, a retry can send it again.

For important transactional mail, record an application-level event or idempotency key, retain the provider message ID, and process delivery webhooks. That gives support a way to answer “was this sent?” without searching worker logs.

A practical rule

Use Mail::raw() when all four statements are true:

  • The body is intentionally plain text.
  • The message is one-off or operational.
  • You do not need a reusable template or text/HTML pair.
  • Synchronous delivery is acceptable, or you have wrapped the send in a deliberate queued job.

If one of those stops being true, promote the message to a mailable. Do not wait until the same anonymous callback has been copied into four controllers.

Send it through a real domain

The content API is only half of production email. The configured transport still needs an authenticated From domain, visible errors, and delivery events. For the complete application structure, read Send email in Laravel. For installation and the first request, follow the ArawaMail Laravel quickstart.

Create a free ArawaMail workspace, connect the domain, and send the first real message through the configured transport instead of PHP mail() or Laravel's log driver. Keep Mail::raw() for the small plain-text job it was designed to do.

Simple, transparent plans

Start free. Grow when your email does.

Get one domain, API access and 3,000 transactional emails every month at no cost.

Compare plans