
We built Mailbox for Laravel to answer a question every Laravel team runs into: what does this email actually look like before it reaches a real inbox? It captures your app’s outgoing mail and shows it in a dashboard inside the app, with no external service and no extra process. Since we open-sourced it, Mailbox has been installed more than 35,000 times from Packagist, and teams now run it well beyond their own laptops: on shared staging servers, review apps and CI.
Mailbox 3.0, released on October 6, is built for that wider use. Its focus is security and stability. Captured email stays private by default, previews are sanitized more thoroughly, the dashboard does far less work on every refresh, and attachments, search, and delivery now behave correctly in the edge cases. Here is what changed, how it works under the hood, and how to upgrade.
What’s new in Mailbox for Laravel 3.0?
- Private by default. The dashboard opens in your local environment out of the box. Staging and other shared environments get access through a gate you define.
- Stronger email previews. Captured HTML is now sanitized on a parsed DOM tree instead of with pattern matching.
- A faster dashboard. A full page of attachments loads with one query, however many messages are on screen.
- More reliable capture. Thirteen fixes across attachments, search, message deduplication and delivery.
- Laravel 11, 12 and 13, with CI now running PHP 8.5 for Laravel 12 and 13.
How does Mailbox 3.0 keep captured email private?
The dashboard is now local-only by default, and every other environment needs a gate you write yourself.
Captured emails often hold the most sensitive links an application sends: password resets, magic sign-in links, invitations. On a developer’s machine that is fine. On a staging server with a public URL, it needs an explicit decision about who can look.
In 2.x, the built-in viewMailbox gate allowed a request when the app was local or when mailbox.enabled was true, and mailbox.enabled defaults to true everywhere except production. That made the dashboard open on any environment where it was installed. In 3.0 the default gate allows APP_ENV=local and nothing else. Anywhere else, requests get a 403, or a redirect tomailbox.unauthorized_redirect, until you grant access:
use Illuminate\Support\Facades\Gate;
public function boot(): void
{
Gate::define('viewMailbox', fn ($user) => $user?->isAdmin());
}
The redirect itself is smarter too. unauthorized_redirect now applies only to guests, so a signed-in user without access gets a clear 403 instead of being sent to a login page that would send them straight back.
How does Mailbox sanitize email previews?
Every captured email is now parsed into a DOM tree and cleaned node by node before it is displayed.
Earlier versions removed dangerous markup with regular expressions: <script> blocks, double-quoted on* handlers and quoted javascript: URLs. HTML has too many valid spellings for patterns to catch reliably, such as </script >, unquoted handlers, obfuscated schemes, data:text/html links, <meta http-equiv="refresh"> and <base>. 3.0 parses captured HTML with the browser’s DOMParser, which never executes scripts, then works on the tree:
- active elements are removed
- every
on*attribute is stripped - unsafe URL schemes are removed from
href,src,action,formactionand similar attributes targetandformtargetare removed, and links open in a new tab, so nothing inside an email can navigate the dashboard
The message list snippet is extracted the same way. All of this sits behind the preview iframe’s sandbox, which never allows scripts, so the two layers now protect the dashboard independently. The change also resolves the CodeQL alerts GitHub had raised on the old sanitizer.
How did the dashboard get faster?
The dashboard now loads every attachment on a page with a single query.
Mailbox polls for new mail every five seconds, 20 messages per page. Before 3.0, each poll asked the attachment store for attachments once per message, then once more for every inline image, which added up to dozens of queries per refresh. 3.0 adds findByMessages(array $messageIds) to the AttachmentStore contract. The database driver answers it with one whereIn, and the inline-image rewriter reuses the attachments already loaded. On a page of five messages with two inline images each, attachment queries drop from 15 to 1.
What’s more reliable in Mailbox 3.0?
The release notes list thirteen fixes. The ones teams are most likely to notice:
- Delivery never waits on capture. When you run Mailbox in decorate mode, capturing locally and forwarding to a real mailer, a capture failure such as a locked SQLite file or a full disk no longer stops the real email. The error is reported and delivery continues.
- Duplicate sends are recognised. Symfony adds the
Message-IDto a clone of the message, so earlier versions never saw it. Mailbox now takes the ID from the sent message, and a resend with the same Message-ID updates the existing capture instead of creating a second one. - Attachment bytes stay exactly as sent. Attachment content is always stored as raw bytes, so files that happen to look like base64 are no longer decoded by mistake. Code that holds base64 uses the new
AttachmentData::fromBase64(). - Filenames in any language download correctly. Downloads now build
Content-Dispositionwith Symfony’sHeaderUtils, sending the original name as RFC 5987filename*with an ASCII fallback. A file calledანგარიში.pdfarrives with its Georgian name intact. - Search works the same on every database. Searches containing
_or%now match on SQLite, and Postgres search is case-insensitive throughILIKE, using one explicitESCAPEclause that works across SQLite, MySQL, Postgres and SQL Server. - Setup is smoother.
mailbox:installfinds its migrations wherever the package is installed, andmailbox:install --devworks in any app and on Windows.
The “Send test email” button now runs through the full capture pipeline, normalizer and attachment storage included, so a successful test confirms the whole path.
How do we keep Mailbox reliable from here?
Alongside the fixes, 3.0 adds guardrails that check every future change.
The package’s architecture tests are now real Pest arch() rules. They enforce layer boundaries between HTTP and storage, which contracts each store implements, naming, strict types and the absence of debug calls, and every file in src/ declares strict_types. A new frontend workflow type-checks and builds the dashboard on every change and fails if the shipped assets differ from a fresh build, so the dashboard you install always matches its source. And the package’s Laravel Boost rules, the guidance AI coding agents read, now describe each command’s real effect, including that mailbox:install --refresh wipes captured messages.
How do I upgrade to Mailbox for Laravel 3.0?
On local development with default settings, the upgrade is two commands:
composer require --dev redberry/mailbox-for-laravel:^3.0
php artisan vendor:publish --tag=mailbox-assets --force
The second command publishes the rebuilt dashboard assets that ship with 3.0. Then check whether any of these apply:
- Still on Laravel 10? Stay on 2.x until you upgrade Laravel.
- Use the dashboard on staging or a review app? Define your own
viewMailboxgate, as shown above. - Wrote a custom attachment driver? Add the bundled fallback trait, which implements the new method with one call per message:
use Redberry\MailboxForLaravel\Storage\Concerns\FindsAttachmentsByMessages;
class RedisAttachmentStore implements AttachmentStore
{
use FindsAttachmentsByMessages;
}
- Build
AttachmentDatawith base64 content? Switch toAttachmentData::fromBase64('report.pdf', 'application/pdf', $size, $base64). - Type-hint
Storage\AttachmentStore? UseContracts\AttachmentStore, orStorage\DatabaseAttachmentStorefor the database implementation.
The full upgrade guide covers each step.
Why is this a major version?
Because 3.0 changes defaults that existing apps may rely on, and semantic versioning says that belongs in a major release.
Teams that open Mailbox on staging today will need a gate after upgrading. Custom attachment drivers need the new method. Laravel 10 drops out of support. Putting those changes behind a new major version means nobody gets them from a routine composer update, and everyone who upgrades does it knowing what to check.
Mailbox for Laravel is open source under the MIT license. The v3.0.0 release notes list every change with its issue, and if you are new to the package, our introduction walks through setup.







