Outbound Mailbox

Rails engine that stores outbound email the same way Action Mailbox stores inbound email: Active Record metadata, raw RFC822 source in Active Storage, a Mail delivery method, and an HTML conductor at /rails/conductor/outbound_mailbox/outbound_emails when enabled — by default if Action Mailer uses delivery_method :outbound_mailbox, or any time via config.outbound_mailbox.mount_conductor.

There is no runtime dependency on Action Mailer in the library; the delivery class implements Mail’s delivery contract so any stack that ends in Mail::Message#deliver! can use it. Typical Rails hosts configure Action Mailer to use this method in development.

Installation

Add to your Gemfile:

gem "outbound_mailbox"

Then:

bundle install
bin/rails generate outbound_mailbox:install
bin/rails db:migrate

Optional: use a dedicated Active Storage service for captured mail (mirrors Action Mailbox’s config.action_mailbox.storage_service):

# config/application.rb
config.outbound_mailbox.storage_service = :local # or any configured service name

To browse captured mail via the conductor without using Action Mailer's :outbound_mailbox delivery method (for example you call OutboundMailbox::OutboundEmail.create_from_mail! yourself or use a custom delivery path), enable routes explicitly:

# config/application.rb
config.outbound_mailbox.mount_conductor = true

Set to false to hide the conductor even when delivery_method is :outbound_mailbox. You can also assign a proc: config.outbound_mailbox.mount_conductor = ->(app) { ... } (must return a boolean-coercible value). The default is nil, meaning mount when config.action_mailer.delivery_method is :outbound_mailbox.

Capturing mail in development

# config/environments/development.rb
config.action_mailer.delivery_method = :outbound_mailbox

This replaces real SMTP delivery in that environment (similar to :test or Letter Opener). Plain Mail is also supported if you assign the same delivery class.

Conductor

By default, when config.action_mailer.delivery_method is :outbound_mailbox, the engine registers conductor routes under:

/rails/conductor/outbound_mailbox/outbound_emails

You can override that with config.outbound_mailbox.mount_conductor; see Installation optional settings above.

List and show stored messages, or Incinerate to delete a record and purge its blob. When routes are not registered, those URLs are not routed (404). Route registration follows config/routes.rb (startup and reload in development).

Testing this gem

Tests boot a single-file Rails app in test/dummy/app.rb. Migrations live under test/dummy/db/migrate.

bundle install
bundle exec rake test
bundle exec rubocop

Continuous integration uses the latest stable MRI Ruby ruby/setup-ruby, ruby-version: "ruby" and exercises Rails 8.1 (default Gemfile), Rails 8.0, Rails 7.1, and Rails 7.0 via gemfiles/rails_8_1.gemfile, gemfiles/rails_8_0.gemfile, gemfiles/rails_7_1.gemfile, and gemfiles/rails_7_0.gemfile. To match a matrix entry locally:

BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle install
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test

The gem’s Gemfile includes actionmailer so config.action_mailer exists in that app; the gemspec does not list action_mailer or actionmailbox as runtime dependencies. Runtime Rails components are constrained to >= 7.0, < 8.2 (actionpack, activestorage, activerecord, railties). The gem requires Ruby 3.1+ (required_ruby_version).

Releasing

Releases are done automatically by GitHub Actions when a new tag is pushed to the repository.

To release a new version, create a pull request updating the "Unreleased" section of CHANGELOG.md file to reflect the upcoming version and expected release date, and to update the version number in lib/outbound_mailbox/version.rb. Once the pull request is merged, create a new tag in the format vX.Y.Z, which will trigger GitHub Actions to publish the new version to RubyGems.

License

MIT — see MIT-LICENSE.