spatie/laravel-mail-preview
A Laravel mail transport that previews sent mail locally. Swap your mailer's transport to 'preview' and a small overlay appears whenever a mail is sent, linking to the rendered message. Add the middleware to the web group to enable it. Handy for testing emails in local environments.
Architecture fit. spatie/laravel-mail-preview is a thin, non-invasive add-on that plugs into three well-defined Laravel extension points: the mail transport registry (config/mail.php), the HTTP middleware stack, and a route macro (Route::mailPreview()). It does not wrap or patch the framework, and it ships an opt-out config (enabled, storage_path, maximum_lifetime_in_seconds, show_link_to_preview, popup_timeout_in_seconds) that lets a team scope its blast radius.
The package delivers two distinct capabilities, and a TPM should evaluate them separately:
.html and .eml, with an in-browser overlay linking to the artifact. This is a developer-experience win for teams iterating on transactional templates (onboarding, invoices, password resets) where Mailtrap/Mailhog adds a round trip.SentMails facade (assertLastContains, assertSent, assertTimesSent, assertNotSent, plus per-message assertSubjectContains/assertTo/assertContains) closes a real gap: Mail::fake does not render the mailable, so content assertions are otherwise impossible without booting the renderer manually. This is the more durable, more defensible reason to adopt, because it produces CI-enforced coverage rather than local convenience.Integration feasibility. Low effort. composer require, one transport string change, one middleware entry in the web group, one route macro, optional vendor:publish for config and views. A competent Laravel engineer can validate it in under a day; org-wide rollout is a documentation exercise, not an engineering one.
Technical risk. Several items warrant scrutiny:
MailStoredEvent as carrying a Swift_Mime_SimpleMessage. Laravel 9+ moved to Symfony Mailer, and the package dropped Laravel 8 in v6. Any consumer writing an event listener against the documented type is working from stale docs — verify the actual payload before building on it.AddMailPreviewOverlayToResponse to the global web group. An unconditional entry in shared kernel code is a production incident waiting on a config mistake. Prefer environment-conditional registration in a service provider.preview transport replaces a real transport. If enabled is true outside debug, mail is written to local disk and never delivered, with no error. This is the single highest-severity failure mode.maximum_lifetime_in_seconds, default 60s for cleanup) must be treated as a data-handling control, not a convenience.Key questions.
Stack fit. Native to Laravel applications on the 9–11 line; the package is a Laravel service provider plus a transport, so it assumes the framework's mail manager, HTTP kernel, and Blade view layer. It is not applicable to Lumen, Symfony-only services, or non-PHP mail pipelines. For a Laravel-dominant estate it fits without architectural negotiation.
Migration path. Two entry points:
themsaid/laravel-mail-preview — the package was transferred from Mohamed Said; an UPGRADING.md exists, and v4.0.1 is explicitly the first release post-transfer, with v5.0.0 a rewrite "to modern standards." Treat this as a namespace and config-shape migration, not a drop-in.Compatibility. Pin to a minor and let Renovate/Dependabot surface upgrades; churn is low (roughly one release per Laravel major plus small fixes). Confirm PHP version support in composer.json before committing. Watch for the v9→v10→v11 pattern where each Laravel major arrives as a dedicated compatibility release — plan a small quarterly upgrade slot rather than assuming silent forward compatibility.
Sequencing.
require --dev, transport swap behind APP_DEBUG, middleware registered conditionally rather than unconditionally. Measure: does the overlay survive our actual response types (Inertia, Livewire, JSON)? That is the most likely first breakage.SentMails assertions to an existing mail test suite. This validates the higher-value half of the package before any org-wide recommendation.enabled env gate to the standard service template.Maintenance. Vendor-maintained by Spatie under MIT, with an active changelog and a clear maintainer handoff history. Surface area is small — a transport, a middleware, a route macro, a facade, and two Blade views — so the internal cost is essentially "keep the version pinned and upgrade annually." The published views and config create a fork risk if anyone customizes them; discourage view publishing unless a concrete styling need exists.
Support. Community only: GitHub issues, no SLA, no commercial backing tier. Acceptable for a developer-tooling dependency; unacceptable as a load-bearing component of a customer-facing path. Escalation for a blocked engineer is "pin to a prior version and work around," which must be acceptable to the team.
Scaling. Non-issue by design — it is a per-workstation and per-CI concern. The two scaling-adjacent concerns are (a) disk growth on developer machines and build agents if cleanup is disabled or maximum_lifetime_in_seconds is raised, and (b) concurrency on shared CI runners if multiple jobs write to the same storage_path. Both are configuration problems, not package limitations.
Failure modes (ranked by severity):
preview transport live with enabled true; mail silently discarded. Mitigation: hard environment guard, not just APP_DEBUG; a startup assertion or config test that fails loudly.web middleware appending markup to JSON, streaming, or file-download responses. Mitigation: conditional middleware registration; smoke-test non-HTML endpoints during pilot./spatie-mail-preview publicly reachable on a misconfigured staging host, exposing captured mail. Mitigation: environment gate plus, where possible, network-level restriction.Ramp-up. Very low. A Laravel developer is productive in under an hour from the README; the assertion API is self-documenting and follows familiar Mail::fake idioms. There is no training program required — one internal README page and a standard config snippet suffice. The real ramp-up cost is organizational discipline around the environment gate, which should be encoded in the service template rather than in documentation people may not read.
How can I help you explore Laravel packages today?