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

Laravel Mailroom Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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).
  • The MailroomMessage model for addressesFor(), sender(), envelopeSender().
  • The AddressRole enum for the roles you can ask for (To, Cc, Bcc, From, ReplyTo, envelope roles).
  • The 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.

Implementation Patterns

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.

Gotchas and Tips

  • Breaking removals. The 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.
  • Configurable table name. 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.
  • Migration ordering. The copy-then-drop happens inside a single migration. If you have a long-lived dev database, back it up first; rollback restores the old columns but is not a substitute for a snapshot when you're testing on data you care about.
  • Search behaviour changed under you. % 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.
  • Non-ASCII display names such as "Müller" are now findable. Good news, but it means search results shift for existing fixtures.
  • Search closes the open message. Mirrors Gmail, and it's deliberate: results take over the list pane. Deep-linking to a message with a search term active will show the results list instead — design any bookmarks accordingly.
  • Spam score colours are not configurable here. Green below 2, amber from 2, red from 5 (SpamAssassin's threshold). If you need your own bands, that's a view override, not a config value.

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.

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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo