Skip to content

Changelog ​

Every release of Mailroom, newest first, with what each one added, changed and fixed. Unreleased is what the next one holds.

The same notes are in CHANGELOG.md on GitHub.

Unreleased ​

Changed ​

  • The mailbox's tab icon is drawn the same as the mark in its sidebar.

0.10.0 - 2026-10-19 ​

Run php artisan migrate after upgrading. Addresses move into a table of their own, and the migration copies every stored message's addresses across before dropping the old columns. It can be rolled back.

Added ​

  • A sidebar of mailboxes. Inbox, Unread (for a signed-in reader) and Spam checked, which lists every scored message highest score first, so the one worth looking at is at the top. The sidebar shows one view at a time, like folders: a mailbox, a person or a mailer, with search narrowing whichever is showing.
  • People. The sidebar lists the ten addresses on the most messages, as sender, recipient, Cc or Bcc, with a count of the rest, and clicking one lists everything it appears on. A search box finds anyone else by address or name.
  • Search as you type. The message search and the people search both update a moment after you stop typing, with the matches highlighted. The message search keeps the address bar in step and, as in Gmail, closes the open message to show the results. An × in either box, or Esc, clears it.
  • Keyboard shortcuts. j and k open the next and previous message, and / jumps to search. Nothing else is bound.
  • Coloured counts. Attachment counts are blue, and a spam score is green below 2, amber from 2, and red from SpamAssassin's threshold of 5, in the list, the tab row and the spam pane alike.
  • An address API on the message. addressesFor(AddressRole::To), sender() and envelopeSender() read the new mailroom_addresses table, and the AddressCollection they return has display(), emails() and includes(). The table name is database.addresses_table.
  • A tab icon. The mailbox names its own icon, inline, so its tab stands apart from the application's and the browser stops asking for /favicon.ico.

Changed ​

  • Confirmations are part of the page. Delete and Clear all ask in the mailbox's own dialog, naming the message or the count, with Cancel focused. Forwarding asks inside its dialog: the first Send names the address, and the second sends.
  • The mailbox is redesigned. Three columns (sidebar, list, message), the message in a card with an icon toolbar, segmented tabs, and the recipients line opening the full set of addresses in place of a Details button. Mailers are sidebar views in place of the mailer select, and appear only when more than one mailer has captured mail.
  • Rows lead with the recipient. In a dev mailbox the sender is nearly always the application itself, so each row shows who the message went to, with an avatar.
  • Tablets and phones get layouts of their own. The sidebar becomes a rail of icons on a tablet. On a phone the list and the message are separate screens, the toolbar shows its labels, and the forward dialog sits at the bottom of the screen.
  • Addresses are rows in mailroom_addresses, one per address and role, with the display name as it appeared on the message.

Removed ​

  • The from, to, cc, bcc, reply_to, envelope_recipients and envelope_sender columns on mailroom_messages, and the model attributes of the same names. Read addresses through $message->addressesFor(AddressRole::To) and the other roles, $message->sender() and $message->envelopeSender(). Anything querying those columns directly needs to query mailroom_addresses.
  • MailroomMessage::formatAddressList() and formatAddress(). Use addressesFor(...)->display() for a list and MailroomAddress::display() for one address.

Fixed ​

  • Searching for "address" or "name" no longer matches every message, and a display name with non-ASCII characters, such as "Müller", can be found.
  • Searching for text containing % or _, such as "50%" or "user_id", finds that text on SQLite and SQL Server, where it found nothing.
  • Search ignores case on PostgreSQL, as it does on the other databases.
  • Deleting a message returns to the list it was opened from, with its search, filters and page, rather than to the first page of everything.

0.9.0 - 2026-09-19 ​

Run php artisan migrate after upgrading. The spam check keeps its result on the message, which needs three new columns.

Added ​

  • A spam check, through Postmark's SpamCheck API. A message can be scored by SpamAssassin from its own pane, which shows the score, the rules that fired, and what each one cost. Set MAILROOM_SPAM_DRIVER=postmark to switch it on, then check a message whenever you want, and check it again after you change the template.

    This is the one feature that sends captured mail out of your application: the whole message goes, attachments included. So the route does not exist without a driver, checking needs a signed-in user outside local, the pane says what it sends before it sends it, and nothing leaves on anything but a click.

    The score is SpamAssassin's rather than ours, and it is blind to authentication and reputation. Mailroom captures before your relay signs and sends, so there is no SPF, DKIM or DMARC result to read, no Received chain, and no sending IP or domain to look up. The content and the headers are what get scored.

Changed ​

  • Counts in the tab row are badges now, the same ones the message list uses for an attachment count. One fact was being written two ways, faintly beside a tab and as a pill in the list. This is what the attachment count looks like as well, and a spam score over the threshold takes the accent colour.

0.8.0 - 2026-09-16 ​

Run php artisan migrate after upgrading. The mailbox needs the new table before it can track what has been read.

Added ​

  • Read and unread, for signed-in users. Opening a message marks it read. Unread rows keep the bolder subject and carry a dot, and the header counts what is still unread, following whatever filter is applied. A message can go back on the pile with Mark unread, and a whole page can be marked either way at once.

    It is per person, so two testers sharing a staging mailbox never clear each other's mail. Where nobody is signed in there are no markers, no controls and no count, so a local mailbox with no login looks exactly as it did.

0.7.0 - 2026-08-31 ​

Added ​

  • A Details disclosure in the message header. Cc, Bcc, Reply-To, tags and metadata sit behind it, with the full labelled list of every address. Whether it is open is remembered between messages.

Changed ​

  • The message header is about half as tall, and now the same height for every message. It used to be three stacked blocks, so one carrying Cc, Bcc and Reply-To pushed the email much further down the pane than a plain one, and the body shifted as you moved through the list.

    It is two rows now: subject and actions, then who it came from, who it went to and when. Opening Details replaces that line rather than repeating it, so every address appears once and in full. Nothing was dropped, and the Headers tab still lists every recorded header.

  • The README names queue workers as the usual cause of missing attachment bytes. A worker captures the mail and writes the files the web process reads back, so running them as separate containers on a local disk breaks downloads the same way an ephemeral platform does. It was documented only under Laravel Cloud before, where nobody on a single server would think to look.

Fixed ​

  • Small text that failed WCAG AA contrast, the preview line under every subject in the message list included. It now clears the minimum for its size, which is most of why the list was harder to read than it looked.

0.6.0 - 2026-08-23 ​

Added ​

  • Desktop, tablet and mobile preview. The rendered body can be viewed at 768px and 375px as well as full width, so you can see whether an email holds together on a phone without forwarding it to one. An iframe carries its own viewport, so the message's own media queries fire at the chosen width — this is a real layout at that size, not a scaled picture of one.

    Desktop is the default and is unchanged from 0.5.0, so nothing looks different until you pick another width. The choice is remembered between messages.

Changed ​

  • composer serve no longer stops after five minutes. Composer applies a 300-second timeout to every script it runs, which was killing the demo server mid-session; the serve script now clears it for itself, leaving test and lint bounded as before. This affects working on the package rather than applications that install it.

Fixed ​

  • The message list showed a line of CSS as the preview snippet for any message whose HTML carried a <style> block, which most templated mail does. Only the tags were being removed, leaving the stylesheet itself to stand in as the summary; the contents of <script> elements arrived there the same way. Both are now dropped along with their tags.

0.5.0 - 2026-08-16 ​

Added ​

  • MAILROOM_FORWARD_ALLOWED bounds where a message may be forwarded. Entries are whole addresses, or @domain to permit everyone there. Left empty, which is the default, any address is permitted — so nothing changes for an existing install until you set it.

  • MAILROOM_FORWARD_RATE_LIMIT caps forwards a minute, counted per signed-in user and otherwise per IP, defaulting to ten. Set it to null to remove the ceiling. Counting runs through your cache store, as Laravel's throttling does.

    Exceeding it reopens the dialog with an explanation rather than returning a browser-facing 429, so a refusal reads like every other forwarding failure.

0.4.0 - 2026-08-06 ​

Added ​

  • A Forward button on each message. Set MAILROOM_FORWARD_MAILER to a mailer that can deliver and a tester can send one message to a real inbox to see how it renders in Gmail or Outlook. Nothing is sent without a click, so SMTP covers only what someone actually checked.

    Sending to the original recipient replays the stored MIME byte for byte. Sending anywhere else rewrites To, drops Cc, and keeps the originals as X-Mailroom-Original-To and X-Mailroom-Original-Cc. Only the chosen address receives a copy, and the stored message is never modified.

    The button is present whether or not forwarding is configured — without a mailer it explains how to switch it on, which is how anyone finds out the feature exists. The route that does the sending is never registered until you configure one.

  • MAILROOM_FORWARD_REQUIRE_AUTH, on by default. Outside the local environment a forward needs a signed-in user, so opening the mailbox up with a permissive Mailroom::auth() callback grants reading without also granting the ability to send from your domain.

0.3.2 - 2026-08-06 ​

Fixed ​

  • A database refresh no longer deletes captured mail during a test run. Laravel's RefreshDatabase runs migrate:fresh as it boots each test, which fired the storage cleanup added in 0.3.1 — before a test could call Storage::fake(), so it emptied the real disk. Anyone running their suite lost the mail they had captured while developing. The refresh belongs to a throwaway test database, so it is no longer acted on while tests are running.

0.3.1 - 2026-08-05 ​

Fixed ​

  • migrate:fresh and migrate:refresh left every captured message's .eml and attachments on disk. Wiping your database now takes the stored files with it. Upgrading will not reclaim orphans from earlier releases — run php artisan mailroom:clear once to remove those.
  • Storage is emptied only when the refreshed connection is the one Mailroom uses, so a migrate:fresh elsewhere leaves mail on a separate MAILROOM_DB_CONNECTION intact. migrate:rollback and migrate:reset still leave files behind.
  • mailroom:clear reported Cleared 0 captured message(s) while deleting orphaned files. It now reports the stored files separately from the row count.

0.3.0 - 2026-08-04 ​

Contains a breaking change. If you set MAILROOM_FORWARD, remove it — see below for why, and for what replaces it.

Added ​

  • A Staging and QA section in the README. Mailroom is as useful on a shared staging environment as it is locally: testers read captured mail in the browser without a third-party catcher, without shared inbox credentials, and without mail leaving your infrastructure. Covers enabling it where the platform reports APP_ENV=production, gating it behind your own login, and pointing the disk at persistent storage.

Removed ​

  • mailroom.forward and MAILROOM_FORWARD. Naming another mailer there relayed every captured message on send, to the message's original recipients — so pointing it at working SMTP on a staging environment delivered test mail to real customers. It also billed an SMTP send for every message whether or not anyone wanted it delivered.

    Deliberate, per-message forwarding from the mailbox replaces it in 0.3.1: everything is captured, and a message goes out only when someone opens it and chooses to send it, which is the point at which you want a real client to render it.

    If you relied on capture-and-deliver, keep a second mailer configured and select it directly for the mail you want delivered.

Changed ​

  • The transport is now terminal in all configurations, which removes the self-forward and recursion guards it needed to carry.

0.2.0 - 2026-08-01 ​

Contains breaking changes. Two public surfaces were removed, so this is a minor rather than a patch release. If you pinned ^0.1, Composer resolves that to 0.1.* and will not pick this up until you bump the constraint deliberately.

Added ​

  • mailroom:install can now run the migrations, so installation is a single command. It refuses to do so while the package is disabled, because the migrations are not registered in that state and running them would only execute the application's unrelated pending migrations.
  • Flags for every prompt, so the installer can run unattended: --set-mailer, --migrate, --no-migrate, --no-config.

Changed ​

  • The README no longer implies mailroom:install is required. It is a convenience: the provider is auto-discovered, config is merged, migrations are loaded from the package, and the mailroom mailer is registered, so composer require plus migrate plus MAIL_MAILER=mailroom is enough.

Removed ​

  • The mailroom-migrations publish tag. Publishing put a copy in database/migrations that ran regardless of MAILROOM_ENABLED, defeating the guard that keeps a production deploy from gaining the tables. Table names and the connection are already config-driven; schema changes belong in your own ALTER migration.
  • The global Mailroom class alias. It was never a facade, and the class is imported normally.

0.1.0 - 2026-07-30 ​

First release.

Added ​

  • A mailroom mail transport that captures every outgoing message: HTML and text bodies, attachments, inline images, tags, metadata and custom headers. It hooks in as a Symfony transport, so it sees the finished message rather than reconstructing one.
  • An auth-gated mailbox at /mailroom with search, a per-mailer filter, pagination, and light, dark and system themes. No build step and no published assets — the styles are inlined.
  • Attachment previews in a lightbox: images, SVG, PDF, audio, video, text, Markdown, CSV and TSV as tables, pretty-printed JSON, source files, .ics invites and nested .eml messages. Media supports HTTP Range, so seeking works. Office documents and archives are download-only.
  • Export as .eml, or as a standalone .html file with embedded images rewritten to data: URIs.
  • Three levels of access control: local-only by default, a viewMailroom gate, or a Mailroom::auth() callback. The package ships no login page and defers to the host application's.
  • mailroom:install, mailroom:prune and mailroom:clear commands. The message model is Prunable, so model:prune works too.
  • A MessageStored event carrying the persisted row, which Laravel's own MessageSent does not provide.
  • Optional forwarding, so a captured message can also be delivered through another mailer.

Security ​

  • Disabled in production by default. The transport throws rather than silently accepting mail, and the mailbox routes are never registered.
  • Attachment previews serve a content type taken from an allowlist, never the attachment's own. Anything text-shaped, .html included, is escaped into the page rather than served, and SVG is rendered only through <img> under a sandbox content policy.
  • Attachment downloads are always application/octet-stream with Content-Disposition: attachment.

Released under the MIT License.