statamic/mailables-viewer
Browse and preview Laravel mailables inside Statamic's control panel. Mailables Viewer surfaces your mailable classes, renders each email, and lets you inspect HTML and text output without leaving the CMS, making it easier to review templates, spot layout issues, and confirm content before mail goes...
Install as a dev dependency — it has no business shipping to production:
composer require --dev statamic/mailables-viewer:^1.1
Statamic's addon auto-discovery registers the service provider; there are no migrations, no config publication, and no application code changes required. After install:
app/Mail location, namespace-scanned).The first real use case: you've just written or edited a WelcomeEmail mailable. Instead of wiring up a route or hitting Mail::to(...)->send() against Mailpit, open the viewer, click the mailable, and eyeball the rendered HTML, the plain-text alternative, the subject line, and the to/cc/bcc it resolves to. That's the whole loop.
The 1.1.0 deep-link capability is the part worth wiring into your habits immediately. Once a preview is open, the URL carries a ?mailable= parameter (an FQCN). Copy that into a Jira ticket or PR description and a designer can open the exact rendered email without hunting through a list:
/cp/mailables?mailable=App\Mail\WelcomeEmail
Treat that URL as the review artifact for any email copy change.
The "design review" flow. When marketing or design needs to sign off on transactional copy — receipts, resets, onboarding — commit the change, drop the deep link in the ticket, and let them open it themselves behind CP auth. This replaces the "engineer triggers a send, screenshots it into Slack" loop. Adopt a convention where every email-affecting PR includes at least one ?mailable= link in its description; that's the cheapest way to make it a definition-of-done item rather than a demo.
Pair it with a catcher, don't replace one. Use the viewer for what the mailable renders and Mailpit/Mailtrap for what actually goes over the wire (headers, deliverability, recipient behavior). The viewer never sends, so it cannot verify Mail::assertSent-style wiring, queued delivery, or attachments-on-the-wire. Keep your Laravel mailable assertions as regression coverage.
Smoke-test matrix before you trust it. Deliberately exercise three shapes of mailable, because they fail differently:
__construct(public string $name)) — the happy path.ShouldQueue / Mailable with content() or markdown() views — verify the Markdown-to-HTML path renders (and that the plain-text alternative is what you expect).Localized / multi-locale mailables. Because rendering happens in a request context, Mail::to(...)->locale('fr') semantics don't apply here the way they do in a real send. If your mailables branch on app()->getLocale(), you're previewing whatever the CP session locale is. For copy review across locales, verify each locale individually rather than assuming the preview represents all of them.
Attachments and inline images. Mailables with attach() or attachFromStorage() may not resolve cleanly in the preview context (missing file, wrong disk, storage not public). Keep the mailables you preview frequently free of heavy attachment logic, or guard attachment setup behind a check that tolerates the preview context.
Use it for new-mailables development. The tightest loop is: write the mailable and Blade view, refresh the preview, iterate on layout. That's faster than any send-and-check workflow and gives you HTML and plain-text side by side — the plain-text view is the one people forget, and the viewer surfaces it for free.
The ?mailable= parameter is the risk surface. Resolving a class name from a query string means instantiating whatever FQCN is passed. Before relying on it: confirm whether the addon validates against a discovered-mailable allowlist or namespace prefix, or accepts raw FQCNs. If raw, an attacker with CP access — or anyone, if the route leaks past auth — can instantiate an arbitrary class, which is an information-disclosure and side-effect vector. Verify by passing a nonsense class and an unrelated FQCN you know exists; check that both fail gracefully (404 or "not found") rather than throwing a framework exception page.
Gate it to non-production. Previews render real content and can surface recipient data or internal-only copy. require-dev alone is not a guard if someone runs composer install --no-dev against a deploy that kept the route cached. Verify from outside the CP that the route is unreachable in production — don't just read the config. Belt-and-braces: an environment check in the addon config or a route middleware restriction if the addon supports it; otherwise an APP_ENV guard at the web server / Statamic route level.
Invalid or stale mailable= values. Once a link is in a ticket, it decays. Renaming or moving a mailable class breaks every saved link with no compile-time protection. Two mitigations: link from the viewer list where possible (so the link always points at the current list), and record the FQCN next to the URL in the ticket so the intent survives the break. If a non-engineer reports "the email link is broken," the first question is "did the class get renamed?" — not "is the addon down."
Constructor dependencies are a known limitation, not a bug. A mailable requiring a User, Order, or DTO can't be instantiated without one. Expect blank renders, a "not found" entry in the list, or a hard error depending on how discovery works. Document it for your reviewers so it isn't filed as a defect, and if you need to preview such mailables regularly, either add defaultable/factory-friendly constructor defaults or create a thin preview-only wrapper mailable with stub data.
No config to publish, but that also means no obvious extension point. If you need to change discovery paths or add an allowlist, you'll be reading the service provider source or vendoring. Forking is cheap — the package is small — but pin to ^1.1 and note it as a review item on every Statamic major upgrade.
Low bus factor. 11 stars, single contributor on the deep-link feature, thin release cadence. That's fine for dev convenience tooling with a near-zero exit cost (a route + controller + Blade wrapper), but don't let it become load-bearing in an onboarding doc without noting the fallback.
Debugging a blank preview. Work outward: (1) does the mailable appear in the index at all (discovery)? (2) does it render without the ?mailable= param (selection)? (3) does it render in a tinker $mailable->render() (the mailable itself)? That isolates addon issues from your own render errors. php artisan view:clear and a fresh CP login solve a surprising share of "it's broken" reports — view caching and stale sessions are common culprits.
How can I help you explore Laravel packages today?