Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mailbook Laravel Package

xammie/mailbook

Mailbook is a Laravel package for inspecting your mails without triggering them in your app. Register mailables and notifications in routes/mailbook.php, then preview them at /mailbook.

View on GitHub
Deep Wiki
Context7

Getting Started

Mailbook is a dev-only mail previewer for Laravel. You register mailables and notifications in a dedicated route file, then browse them at /mailbook without triggering the actual application flow that would normally send them.

Minimal setup:

composer require --dev xammie/mailbook
php artisan mailbook:install

The install command creates routes/mailbook.php. Register your first mailable there:

use App\Mail\VerificationMail;
use Xammie\Mailbook\Facades\Mailbook;

Mailbook::add(VerificationMail::class);

Then visit /mailbook in your browser.

Where to look first:

  1. routes/mailbook.php — this is your workspace; everything you register lives here.
  2. config/mailbook.php — publish it early (php artisan vendor:publish --tag="mailbook-config") because the most useful features (database_rollback, locales, send) are off by default.
  3. The mailbook UI itself — check the device preview, "Sent from" / reply-to details, and attachments panel.

First use case: you've just written a new mailable with a Blade view and need to see it. Instead of wiring up a controller, a route, and a queue worker just to look at the output, register it with a closure that builds fake data:

Mailbook::add(function (): VerificationMail {
    return new VerificationMail(User::factory()->make(), '/example/url');
});

Change the Blade file, hit refresh, and iterate on the design. This tight feedback loop — not "sending test emails to yourself" — is where Mailbook earns its place in day-to-day work.

Implementation Patterns

Register by class when the container can build it. Mailbook::add(VerificationMail::class) uses dependency injection, so service-like constructor arguments resolve automatically. Reach for a closure as soon as you need models, factories, or per-scenario data.

Closures get dependency injection too. Useful when a mailable needs a service and you want to control the rest of the arguments:

Mailbook::add(function (VerificationService $service): VerificationMail {
    return new VerificationMail($service, User::factory()->make(), '/example/url');
});

Notifications need a notifiable. Mailbook::add(InvoiceCreatedNotification::class) works, but notification classes usually need a recipient. Set it with ::to():

Mailbook::to(User::factory()->create())->add(WelcomeNotification::class);
Mailbook::to('[email protected]')->add(WelcomeNotification::class);

Group by category, and hoist shared context. Categories keep the sidebar navigable once you have more than a handful of mails; group() pushes the notifiable (and category) into every registration inside it:

Mailbook::to('[email protected]')
    ->category('Invoices')
    ->group(function () {
        Mailbook::add(InvoiceCreatedNotification::class);
        Mailbook::add(InvoicePaidNotification::class);
    });

Variants are the main lever for design review. Rather than registering five near-identical mailables, add variants to one registration so the sidebar shows the scenarios side by side — one-item vs. two-item orders, empty state vs. populated, long names vs. short:

Mailbook::add(OrderCreatedMail::class)
    ->variant('1 item', fn () => new OrderCreatedMail(Order::factory()->withOneProduct()->create()))
    ->variant('2 items', fn () => new OrderCreatedMail(Order::factory()->withTwoProducts()->create()));

Turn on database rollback as soon as you use create(). With 'database_rollback' => true, factory-created records are cleaned up after rendering, so you can register mails that hit the database without polluting your local dev database:

Mailbook::add(function (): OrderShippedMail {
    return new OrderShippedMail(
        Order::factory()->create(),
        Tracker::factory()->create()
    );
});

Localization is a config list, not a translation system. Add locales to config/mailbook.php and Mailbook renders a dropdown so you can flip a mailable into each language:

'locales' => [
    'en' => 'English',
    'nl' => 'Dutch',
    'de' => 'German',
],

Use the send button for real-client verification. Enable 'send' => true and 'send_to' => '[email protected]' to get a top-right button that delivers the selected mail through your default mail driver. This is the bridge between "looks fine in the browser preview" and "renders correctly in Gmail/Outlook."

Team workflow that actually pays off:

  • Commit routes/mailbook.php. It doubles as a catalog of every mail the app sends — useful in code review and onboarding.
  • Register new mailables at the same time you write them, not later. A registration file that lags behind the codebase is worthless.
  • In PRs that touch mail templates, include Mailbook screenshots for both the default and the edge-case variants.
  • Keep variant names descriptive ('past due date', 'trial expired') — they're read by designers and PMs, not just you.

Gotchas and Tips

It's a dev dependency — treat the route as one. Install with --dev and never let /mailbook reach production. The config includes an option to disable the routes (0.0.7); verify the published config is respected in your deployed environments, and remember that routes/mailbook.php is loaded by the package, not by routes/web.php, so your usual web middleware group assumptions may not apply automatically. If you need auth in a shared staging environment, configure it deliberately and confirm it by hitting the URL while logged out.

database_rollback is disabled by default. This is the single most common surprise: you add Order::factory()->create() to a registration and your local database slowly fills with junk rows. Enable the config flag before you start using factories, not after.

Rollback wraps the render — don't rely on it for side effects. Anything your mailable does outside the transaction (queued jobs, cache writes, files on disk, writes on a different DB connection) won't be undone. Keep registrations side-effect free; if a mailable performs queries, prefer make() over create() where possible.

Dependency injection resolves services, not models. Mailbook::add(SomeMail::class) will fail or misbehave if the constructor needs an Eloquent model or a scalar. Wrap it in a closure. Likewise, if a registration throws, the whole page tends to break rather than just that one entry — comment out registrations to bisect when the UI goes blank.

Queued mailables were broken until 1.5.3. If a ShouldQueue mailable blows up or the preview misbehaves on an old version, upgrade first. Similarly, embedded/inline attachments were only fixed in 1.11.1 — if your logo shows as a broken image in preview but works in real mail, that's the fix you need. Attachments as a feature landed in 1.0.0.

A 404 at /mailbook is usually not a Mailbook bug. Clear cached routes and config (php artisan route:clear, php artisan config:clear), check APP_URL, and confirm the install command actually wrote routes/mailbook.php. Laragon-specific 404s were fixed back in 1.5.2.

Config keys are your extension points. Publish the config to change the route file path, the route domain (added in 1.10.0), the enabled state of routes, and the credits footer. Publish the views (--tag="mailbook-views") only if you genuinely need to reskin the explorer — it's an ongoing maintenance cost as the package evolves.

Notifications differ from mailables in one key way. A missing notifiable produces a confusing render rather than an obvious error, so always pair notification registrations with ::to(). Passing an email string is fine when the notification only uses the mail channel; pass a real model when the notification touches the notifiable's attributes.

Don't over-abstract your registrations. A little duplication between closures is fine and keeps each scenario readable. Extract shared setup into small helper functions in the route file rather than building a registration DSL — Mailbook's value comes from the registrations being obvious at a glance.

Performance degrades with heavy factories. Every mail in the sidebar costs its closure's queries to render. With dozens of registrations building deep object graphs, the explorer gets slow. Keep factory states minimal (make() where the mailable doesn't query) and reserve create() for the mails that actually need persisted relations.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log