outbound_mailbox 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2593221e73c6c30a39d3bf5823ab1daff84827965f17352ef4970ff44ad6609b
4
+ data.tar.gz: f2b88374df3d4e60c348ea100b56a09aa284764af41d1854588ee29a7062e6ec
5
+ SHA512:
6
+ metadata.gz: fd2758390597ed5b28ddb4508fcddab6b7814b099e901c04dd053f88583765784952d7de9c241c821b38cee03b6b0bc1dc83f1111c5bbdb6b8fcd0c84a5ff240
7
+ data.tar.gz: '04084ae34c19dfafcd23691c8fe8ade5611ec8eab93e10d8c97c436b13d34bda1be67be7af5ccad87afd514f390fa7111a2e3393421ae24b681dc45782c1f6b1'
data/MIT-LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) Ackama
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Outbound Mailbox
2
+
3
+ Rails engine that stores **outbound** email the same way [Action Mailbox](https://guides.rubyonrails.org/action_mailbox_basics.html) 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`.
4
+
5
+ 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.
6
+
7
+ ## Installation
8
+
9
+ Add to your `Gemfile`:
10
+
11
+ ```ruby
12
+ gem "outbound_mailbox"
13
+ ```
14
+
15
+ Then:
16
+
17
+ ```bash
18
+ bundle install
19
+ bin/rails generate outbound_mailbox:install
20
+ bin/rails db:migrate
21
+ ```
22
+
23
+ Optional: use a dedicated Active Storage service for captured mail (mirrors Action Mailbox’s `config.action_mailbox.storage_service`):
24
+
25
+ ```ruby
26
+ # config/application.rb
27
+ config.outbound_mailbox.storage_service = :local # or any configured service name
28
+ ```
29
+
30
+ 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:
31
+
32
+ ```ruby
33
+ # config/application.rb
34
+ config.outbound_mailbox.mount_conductor = true
35
+ ```
36
+
37
+ 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`.
38
+
39
+ ## Capturing mail in development
40
+
41
+ ```ruby
42
+ # config/environments/development.rb
43
+ config.action_mailer.delivery_method = :outbound_mailbox
44
+ ```
45
+
46
+ 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.
47
+
48
+ ## Conductor
49
+
50
+ By default, when `config.action_mailer.delivery_method` is **`:outbound_mailbox`**, the engine registers conductor routes under:
51
+
52
+ `/rails/conductor/outbound_mailbox/outbound_emails`
53
+
54
+ You can override that with `config.outbound_mailbox.mount_conductor`; see **Installation** optional settings above.
55
+
56
+ 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).
57
+
58
+
59
+ ## Testing this gem
60
+
61
+ Tests boot a **single-file** Rails app in [`test/dummy/app.rb`](test/dummy/app.rb). Migrations live under [`test/dummy/db/migrate`](test/dummy/db/migrate).
62
+
63
+ ```bash
64
+ bundle install
65
+ bundle exec rake test
66
+ bundle exec rubocop
67
+ ```
68
+
69
+ Continuous integration uses the **latest stable MRI Ruby** [`ruby/setup-ruby`](https://github.com/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_1.gemfile), [`gemfiles/rails_8_0.gemfile`](gemfiles/rails_8_0.gemfile), [`gemfiles/rails_7_1.gemfile`](gemfiles/rails_7_1.gemfile), and [`gemfiles/rails_7_0.gemfile`](gemfiles/rails_7_0.gemfile). To match a matrix entry locally:
70
+
71
+ ```bash
72
+ BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle install
73
+ BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test
74
+ ```
75
+
76
+ 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`).
77
+
78
+ ## Releasing
79
+
80
+ Releases are done automatically by GitHub Actions when a new tag is pushed to the repository.
81
+
82
+ 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.
83
+
84
+ ## License
85
+
86
+ MIT — see [MIT-LICENSE](MIT-LICENSE).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ require "rake/testtask"
6
+
7
+ Rake::TestTask.new(:test) do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ t.verbose = true
11
+ t.warning = false
12
+ end
13
+
14
+ task default: :test
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Conductor
5
+ module OutboundMailbox
6
+ class OutboundEmailsController < ActionController::Base
7
+ layout "outbound_mailbox/conductor"
8
+
9
+ def index
10
+ @outbound_emails = ::OutboundMailbox::OutboundEmail.order(created_at: :desc)
11
+ end
12
+
13
+ def show
14
+ @outbound_email = ::OutboundMailbox::OutboundEmail.find(params[:id])
15
+ end
16
+
17
+ def destroy
18
+ @outbound_email = ::OutboundMailbox::OutboundEmail.find(params[:id])
19
+ @outbound_email.incinerate
20
+ redirect_to rails_conductor_outbound_mailbox_outbound_emails_path, notice: "Email incinerated"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mail"
4
+ require "openssl"
5
+ require "stringio"
6
+
7
+ module OutboundMailbox
8
+ # Persists the raw RFC822 source in Active Storage (metadata is a checksum only; outbound mail does not need RFC Message-ID for deduplication).
9
+ class OutboundEmail < Record
10
+ has_one_attached :raw_email
11
+
12
+ class << self
13
+ def create_from_mail!(mail)
14
+ source = mail.encoded
15
+ checksum = OpenSSL::Digest::SHA1.hexdigest(source)
16
+ create!(
17
+ raw_email: create_and_upload_raw_blob!(source),
18
+ message_checksum: checksum
19
+ )
20
+ end
21
+
22
+ private
23
+
24
+ def create_and_upload_raw_blob!(source)
25
+ args = {
26
+ io: StringIO.new(source),
27
+ filename: "message.eml",
28
+ content_type: "message/rfc822"
29
+ }
30
+ args[:service_name] = OutboundMailbox.storage_service if OutboundMailbox.storage_service.present?
31
+ ActiveStorage::Blob.create_and_upload!(**args)
32
+ end
33
+ end
34
+
35
+ def mail
36
+ @mail ||= OutboundMailbox.mail_message_from_raw_source(source)
37
+ end
38
+
39
+ def source
40
+ @source ||= raw_email.download
41
+ end
42
+
43
+ def incinerate
44
+ transaction do
45
+ raw_email.purge if raw_email.attached?
46
+ destroy!
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OutboundMailbox
4
+ class Record < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <head>
3
+ <title>Rails Conductor: <%= yield :title %></title>
4
+ </head>
5
+ <body>
6
+ <%= yield %>
7
+ </body>
8
+ </html>
@@ -0,0 +1,24 @@
1
+ <h1>Outbound emails</h1>
2
+
3
+ <table>
4
+ <thead>
5
+ <tr>
6
+ <th>ID</th>
7
+ <th>Subject</th>
8
+ <th>Checksum</th>
9
+ <th>Created</th>
10
+ <th></th>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <% @outbound_emails.each do |email| %>
15
+ <tr>
16
+ <td><%= email.id %></td>
17
+ <td><%= email.mail.subject %></td>
18
+ <td><code><%= email.message_checksum.slice(0, 12) %>…</code></td>
19
+ <td><%= email.created_at %></td>
20
+ <td><%= link_to "Show", rails_conductor_outbound_mailbox_outbound_email_path(email) %></td>
21
+ </tr>
22
+ <% end %>
23
+ </tbody>
24
+ </table>
@@ -0,0 +1,19 @@
1
+ <h1>Outbound email #<%= @outbound_email.id %></h1>
2
+
3
+ <dl>
4
+ <dt>Checksum</dt>
5
+ <dd><code><%= @outbound_email.message_checksum %></code></dd>
6
+ <dt>Created</dt>
7
+ <dd><%= @outbound_email.created_at %></dd>
8
+ </dl>
9
+
10
+ <h2>Headers</h2>
11
+ <pre><%= ERB::Util.html_escape(@outbound_email.mail.header.fields.map(&:to_s).join("\n")) %></pre>
12
+
13
+ <h2>Raw source</h2>
14
+ <pre><%= ERB::Util.html_escape(@outbound_email.source) %></pre>
15
+
16
+ <p>
17
+ <%= link_to "Back to list", rails_conductor_outbound_mailbox_outbound_emails_path %>
18
+ <%= button_to "Incinerate", rails_conductor_outbound_mailbox_outbound_email_path(@outbound_email), method: :delete, form: { data: { turbo: false } } %>
19
+ </p>
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ if OutboundMailbox.mount_conductor_routes?(Rails.application)
5
+ scope "rails/conductor/outbound_mailbox/", module: "rails/conductor/outbound_mailbox" do
6
+ resources :outbound_emails, only: %i[index show destroy], as: :rails_conductor_outbound_mailbox_outbound_emails
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record/migration"
5
+
6
+ module OutboundMailbox
7
+ class InstallGenerator < Rails::Generators::Base
8
+ include ActiveRecord::Generators::Migration
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ desc "Creates an Outbound Mailbox migration."
12
+
13
+ def copy_migration
14
+ migration_template "migration.rb", "db/migrate/create_outbound_mailbox_outbound_emails.rb"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateOutboundMailboxOutboundEmails < ActiveRecord::Migration::Current
4
+ def change
5
+ create_table :outbound_mailbox_outbound_emails do |t|
6
+ t.string :message_checksum, null: false
7
+
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :outbound_mailbox_outbound_emails, :created_at
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OutboundMailbox
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace OutboundMailbox
6
+
7
+ config.generators do |g|
8
+ g.test_framework :test_unit
9
+ end
10
+
11
+ config.outbound_mailbox = ActiveSupport::OrderedOptions.new
12
+ config.outbound_mailbox.storage_service = nil
13
+ # nil = derive from Action Mailer delivery_method (see OutboundMailbox.mount_conductor_routes?)
14
+ config.outbound_mailbox.mount_conductor = nil
15
+
16
+ initializer "outbound_mailbox.storage_service" do
17
+ config.after_initialize do |app|
18
+ OutboundMailbox.storage_service = app.config.outbound_mailbox.storage_service
19
+ end
20
+ end
21
+
22
+ initializer "outbound_mailbox.action_mailer" do
23
+ ActiveSupport.on_load(:action_mailer) do
24
+ ActionMailer::Base.add_delivery_method :outbound_mailbox, OutboundMailbox::MailDelivery
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OutboundMailbox
4
+ # Mail delivery handler (same contract as Mail::TestMailer).
5
+ # Register with Action Mailer via +config.action_mailer.delivery_method = :outbound_mailbox+.
6
+ class MailDelivery
7
+ attr_accessor :settings
8
+
9
+ def initialize(values = {})
10
+ @settings = (values || {}).dup
11
+ end
12
+
13
+ def deliver!(mail)
14
+ OutboundMailbox::OutboundEmail.create_from_mail!(mail)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OutboundMailbox
4
+ VERSION = "1.0.0"
5
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mail"
4
+ require "outbound_mailbox/version"
5
+ require "outbound_mailbox/engine"
6
+ require "outbound_mailbox/mail_delivery"
7
+
8
+ module OutboundMailbox
9
+ mattr_accessor :storage_service, default: nil
10
+
11
+ # True when +delivery_method+ is configured as +outbound_mailbox+ for Action Mailer.
12
+ # See also {.mount_conductor_routes?} for overriding via +config.outbound_mailbox.mount_conductor+.
13
+ def self.conductor_routes_enabled_for_delivery_method?(delivery_method)
14
+ delivery_method&.to_sym == :outbound_mailbox
15
+ end
16
+
17
+ # Whether conductor routes should be drawn (+config/routes.rb+).
18
+ #
19
+ # +config.outbound_mailbox.mount_conductor+ must be one of:
20
+ # * +nil+ (default) — mount only when {.conductor_routes_enabled_for_delivery_method?}
21
+ # is true for +app.config.action_mailer.delivery_method+.
22
+ # * +true+ / +false+ — force conductor routes on or off (ignore delivery method). Use +true+
23
+ # if you persist mail without setting Action Mailer to +:outbound_mailbox+ but still want the UI.
24
+ # * Anything responding to +call+ (+Proc+, method object, ...) — invoked as +callable.call(app)+;
25
+ # the return value is coerced with +!!+ so use a boolean return for clarity.
26
+ def self.mount_conductor_routes?(app)
27
+ raw = app.config.outbound_mailbox.mount_conductor
28
+ case raw
29
+ when nil
30
+ conductor_routes_enabled_for_delivery_method?(app.config.action_mailer.delivery_method)
31
+ when true, false
32
+ raw
33
+ else
34
+ unless raw.respond_to?(:call)
35
+ raise ArgumentError, "config.outbound_mailbox.mount_conductor must be nil, true/false, or callable; got #{raw.class}"
36
+ end
37
+
38
+ !!raw.call(app)
39
+ end
40
+ end
41
+
42
+ # Same behavior as Action Mailbox’s +Mail.from_source+, without pulling in that gem.
43
+ def self.mail_message_from_raw_source(source)
44
+ Mail.new(Mail::Utilities.binary_unsafe_to_crlf(source.to_s))
45
+ end
46
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: outbound_mailbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ackama
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: actionpack
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '8.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '8.2'
32
+ - !ruby/object:Gem::Dependency
33
+ name: activestorage
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '7.0'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '8.2'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '7.0'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '8.2'
52
+ - !ruby/object:Gem::Dependency
53
+ name: activerecord
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '7.0'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '8.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '7.0'
69
+ - - "<"
70
+ - !ruby/object:Gem::Version
71
+ version: '8.2'
72
+ - !ruby/object:Gem::Dependency
73
+ name: mail
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: 2.8.0
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 2.8.0
86
+ - !ruby/object:Gem::Dependency
87
+ name: railties
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '7.0'
93
+ - - "<"
94
+ - !ruby/object:Gem::Version
95
+ version: '8.2'
96
+ type: :runtime
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '7.0'
103
+ - - "<"
104
+ - !ruby/object:Gem::Version
105
+ version: '8.2'
106
+ description: Rails engine that stores outbound mail like Action Mailbox stores inbound
107
+ mail.
108
+ email:
109
+ - hello@ackama.com
110
+ executables: []
111
+ extensions: []
112
+ extra_rdoc_files: []
113
+ files:
114
+ - MIT-LICENSE
115
+ - README.md
116
+ - Rakefile
117
+ - app/controllers/rails/conductor/outbound_mailbox/outbound_emails_controller.rb
118
+ - app/models/outbound_mailbox/outbound_email.rb
119
+ - app/models/outbound_mailbox/record.rb
120
+ - app/views/layouts/outbound_mailbox/conductor.html.erb
121
+ - app/views/rails/conductor/outbound_mailbox/outbound_emails/index.html.erb
122
+ - app/views/rails/conductor/outbound_mailbox/outbound_emails/show.html.erb
123
+ - config/routes.rb
124
+ - lib/generators/outbound_mailbox/install_generator.rb
125
+ - lib/generators/outbound_mailbox/templates/migration.rb
126
+ - lib/outbound_mailbox.rb
127
+ - lib/outbound_mailbox/engine.rb
128
+ - lib/outbound_mailbox/mail_delivery.rb
129
+ - lib/outbound_mailbox/version.rb
130
+ homepage: https://github.com/ackama/outbound_mailbox
131
+ licenses:
132
+ - MIT
133
+ metadata:
134
+ homepage_uri: https://github.com/ackama/outbound_mailbox
135
+ source_code_uri: https://github.com/ackama/outbound_mailbox
136
+ rdoc_options: []
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: 3.1.0
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubygems_version: 3.6.9
151
+ specification_version: 4
152
+ summary: Rails engine that stores outbound mail like Action Mailbox stores inbound
153
+ mail.
154
+ test_files: []