MailboxGem
A development-only email inbox for Rails, inspired by Phoenix's /dev/mailbox. Point ActionMailer at it in development and every email your app actually sends — through its real mailer code paths, not a hand-written preview — shows up in a live, searchable inbox in your browser.
MailboxGem is a standalone engine. It doesn't modify Rails or Action Mailer; it registers itself as one more delivery method, the same extension point letter_opener and mailcatcher use.
Why
Rails already has two ways to look at outgoing mail in development, and both have a gap:
ActionMailer::Preview(/rails/mailers) renders a mailer against data you construct by hand in a preview class. It never touches your real application code path, so it can drift from what actually gets sent.ActionMailer::Base.deliveriesonly accumulates anything whendelivery_methodis:test, which is what thetestenvironment uses — notdevelopment. In development, there's normally nowhere to look at all.
MailboxGem fills that gap: it captures the real Mail::Message your app hands to Action Mailer when a user actually triggers an email (signs up, resets a password, checks out), and gives you a live UI to inspect it — HTML, plain text, and raw source, with attachments.
Demo
https://github.com/user-attachments/assets/16f82b71-7b6c-47fa-8acc-0dbe147975cd
Features
- Captures real mail, not previews — anything delivered through Action Mailer while
mailbox_gemis the active delivery method. - Live-updating inbox — the message list polls in the background and updates without a page reload.
- Search-as-you-type — filters by from/to/subject with no page reload and no query language, debounced so it doesn't hammer the server.
- HTML / Text / Source tabs per message, with attachments listed and sized. HTML is rendered in a sandboxed
iframe— captured mail is untrusted content and never executes scripts against your app's page. - Zero-config storage — in-memory only, no database, no migration to install.
Requirements
- Ruby >= 3.2
- Rails >= 8.0
Installation
Add it to your development group — this is a dev tool and has no reason to load in production or test:
group :development do
gem "mailbox_gem"
end
bundle install
Setup
1. Mount the engine, guarded to development, wherever you'd like it served from:
# config/routes.rb
mount MailboxGem::Engine => "/mailbox" if Rails.env.development?
2. Point Action Mailer at it:
# config/environments/development.rb
config.action_mailer.delivery_method = :mailbox_gem
3. Visit /mailbox and send some mail from your app. It'll show up without you doing anything else.
Both steps are opt-in on purpose. Mounting is a normal route in your own config/routes.rb, and the delivery method only takes effect if you assign it — installing the gem never silently changes how your app already handles mail in development (e.g. if you're already using letter_opener or a local SMTP catcher).
Configuration
MailboxGem keeps the most recent messages and drops older ones once that limit is exceeded:
# config/initializers/mailbox_gem.rb
MailboxGem. = 500 # defaults to 200
Known limitations
- In-memory only. The mailbox is wiped on every server restart. This is deliberate — the same tradeoff
ActionMailer::Base.deliveriesmakes — not a bug. - Single Puma worker only. Storage is a plain in-process buffer. If your app runs Puma in cluster mode (
WEB_CONCURRENCY > 1), each worker process gets its own independent mailbox, and the UI will only show whatever mail happened to land on whichever worker served that request. This is fine under Puma's default (WEB_CONCURRENCYunset, i.e. one worker); it's a real gap if you deliberately run multiple workers in development.
Development
bundle install # installs dependencies for the gem and its dummy test app
bin/rails test # runs the test suite against the dummy app
bin/rubocop # lints
The dummy Rails app used for manual testing lives under test/dummy.
Testing against multiple Rails versions
mailbox_gem.gemspec declares "rails", ">= 8.0". CI backs that claim by running the suite against each supported minor via Appraisal - see Appraisals and the generated gemfiles/*.gemfile:
BUNDLE_GEMFILE=gemfiles/rails_8.0.gemfile bin/rails test
BUNDLE_GEMFILE=gemfiles/rails_8.1.gemfile bin/rails test
After changing Appraisals or the gemspec's dependencies, regenerate and commit the gemfiles:
bundle exec appraisal generate
Contributing
Bug reports and pull requests are welcome. This gem is meant to stay small and dependency-light — before adding a new capability, it's worth asking whether it fits a zero-config, drop-in dev tool, or belongs as configuration a host app opts into instead.
License
Released under the MIT License.