ebbbang/laravel-mailroom
Laravel Mailroom gives your Laravel app a tidy layer for handling email: organize, route, and process incoming messages through a simple, testable API so mail workflows stay out of your controllers and easy to reason about.
This release moves message addresses out of mailroom_messages and into their own mailroom_addresses table, so the first step after upgrading is non-negotiable:
composer require <vendor>/mailroom # or update to ^0.10
php artisan migrate
The migration copies every stored message's addresses across before dropping the old columns, and it can be rolled back — but run it as part of your deploy, before the app serves traffic. On a dev mailbox with a lot of captured mail the copy step is the slow part, so don't run it lazily the first time someone opens the mailbox.
Where to look first:
config/mailroom.php → database.addresses_table (defaults to mailroom_addresses).MailroomMessage model for addressesFor(), sender(), envelopeSender().AddressRole enum for the roles you can ask for (To, Cc, Bcc, From, ReplyTo, envelope roles).MailroomAddress model / AddressCollection for rendering.First use case: browse to the mailbox route, hit the Spam mailbox in the sidebar (highest score first), open the top message, and read its recipients through $message->addressesFor(AddressRole::To)->display(). That single flow exercises the new schema, the new accessors and the new UI at once, which makes it a good smoke test after the migration.
Reading addresses in application code. The old column-attribute style is gone. Everywhere you previously did $message->to or $message->from, go through the roles:
use <Vendor>\Mailroom\Enums\AddressRole;
$message->addressesFor(AddressRole::To); // AddressCollection
$message->addressesFor(AddressRole::Cc);
$message->sender(); // From
$message->envelopeSender(); // envelope sender
AddressCollection gives you the three shapes you actually need in a view or an assertion:
$to = $message->addressesFor(AddressRole::To);
$to->display(); // "Ada Lovelace <[email protected]>, ..." — for UI/logs
$to->emails(); // ['[email protected]', ...] — for comparisons
$to->includes('[email protected]'); // boolean — for tests and guards
Use display() for anything human-facing and emails() / includes() for logic; don't string-match against display(), because it includes display names as they appeared on the message.
Single-address rendering. MailroomAddress::display() replaces the removed formatAddress(). If you had a Blade partial calling formatAddress([$email => $name]), it now takes an address model:
@foreach ($message->addressesFor(AddressRole::To) as $address)
<span title="{{ $address->email }}">{{ $address->display() }}</span>
@endforeach
Querying by address. Anything that used where('to', 'like', ...) on mailroom_messages must move to the address table. Prefer resolving the table name from config rather than hardcoding it, since it's configurable:
$table = config('mailroom.database.addresses_table');
MailroomMessage::whereHas('addresses', fn ($q) => $q
->where('email', '[email protected]')
->where('role', AddressRole::To->value)
)->get();
This is the pattern for "show me everything sent to X" outside the UI — the sidebar's People view does the same thing internally, but you'll want it in your own tooling (seeders, dev dashboards, regression assertions).
Testing captured mail. Assert against addressesFor(...)->includes(...) and ->emails() rather than column values. Because the search fixes land in this release, tests that previously asserted nothing was found for 50% or user_id should now assert a hit — those were genuine bugs, not intended behaviour.
URL state and navigation. Search keeps the address bar in step, and opening a message then deleting it returns to the list you came from with search, filters and page intact. That means URLs are shareable and a test asserting a redirect to the mailbox root will now fail; assert the originating list URL instead.
from, to, cc, bcc, reply_to, envelope_recipients and envelope_sender columns are dropped, along with the model attributes of the same names, plus MailroomMessage::formatAddressList() and formatAddress(). Grep for those eight names before deploying; nothing will warn you at runtime beyond a null or a missing-method error. Third-party code and your own seeders are the usual hiding places.mailroom_addresses is only the default. Hardcoding it in migrations, raw SQL or reporting queries will break anyone who has renamed it. Use config('mailroom.database.addresses_table') or the model/relation.% and _ now work as literal text on SQLite and SQL Server (previously they found nothing), PostgreSQL is now case-insensitive like the others, and "address"/"name" no longer match every message. If you added workarounds for any of these — escaping, lower() wrappers, excluded columns — remove them, or you'll reintroduce the bug you were dodging.Extension points worth knowing: the AddressRole enum is the natural place to drive your own role-aware rendering, AddressCollection::display()/emails()/includes() cover most custom UI without touching the tables, and the sidebar's mailbox/person/mailer views mean you rarely need to hand-roll a "messages from this sender" page.
How can I help you explore Laravel packages today?