mailbin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 97c16b480b7973ca30ed864cceaa479e79457a4d8a6e35bec60a139e00233bf7
4
+ data.tar.gz: 704b7096ef4eaee23689ea7e5e4a1e9b46f61d3feacfed545ff9a3fa7da3b641
5
+ SHA512:
6
+ metadata.gz: c77e8c567e0244ac54027311adef7e0ccc9dda560405e6a188a85ff7c3d2876e5fcec9d59c317f6b924bf6a8109cb96a30e3dc0e97f19ebcd868c80ddd9048fd
7
+ data.tar.gz: 6813cd20e6dd13a3781f4fd48e3326f3d7cdd125bb7ef540bca6bf672e76196d51b85711bc58b85ff31ec6c752983a199546a505716042f1e7f85f84e5d5a8ab
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright Chris Oliver
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # 📬 Mailbin
2
+ Preview emails sent from ActionMailer in the browser.
3
+
4
+ Mailbin writes emails to `tmp/mailbin` for easy access and testing.
5
+
6
+ ## 📦 Installation
7
+
8
+ Add Mailbin to your Gemfile:
9
+
10
+ ```ruby
11
+ bundle add "mailbin"
12
+ ```
13
+
14
+ Configure Rails to send emails to MailDrop:
15
+
16
+ ```ruby
17
+ # config/environments/development.rb
18
+ config.action_mailer.delivery_method = :mailbin
19
+ config.action_mailer.perform_deliveries = true
20
+ ```
21
+
22
+ Add the routes to view emails with MailBin:
23
+
24
+ ```ruby
25
+ Rails.application.routes.draw do
26
+ mount Mailbin::Engine => :mailbin if Rails.env.development?
27
+ end
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ Open http://localhost:3000/mailbin to view your emails in development.
33
+
34
+ ## Contributing
35
+
36
+ If you have an issue you'd like to submit, please do so using the issue tracker in GitHub. In order for us to help you in the best way possible, please be as detailed as you can.
37
+
38
+ If you'd like to open a PR please make sure the following things pass:
39
+
40
+ ```bash
41
+ bin/rails db:test:prepare
42
+ bin/rails test
43
+ bin/rubocop -A
44
+ ```
45
+
46
+ ## License
47
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
6
+ load "rails/tasks/engine.rake"
7
+ load "rails/tasks/statistics.rake"
8
+
9
+ desc "Run tests"
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << "lib"
12
+ t.libs << "test"
13
+ t.pattern = "test/**/*_test.rb"
14
+ t.verbose = true
15
+ t.warning = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../stylesheets/mailbin .css
2
+ //= link_directory ../../javascript/mailbin .js
@@ -0,0 +1,57 @@
1
+ html, body, iframe {
2
+ height: 100%;
3
+ }
4
+
5
+ body {
6
+ margin: 0;
7
+ }
8
+
9
+ header {
10
+ width: 100%;
11
+ padding: 10px 0 0 0;
12
+ margin: 0;
13
+ background: white;
14
+ font: 12px "Lucida Grande", sans-serif;
15
+ border-bottom: 1px solid #dedede;
16
+ overflow: hidden;
17
+ }
18
+
19
+ dl {
20
+ margin: 0 0 10px 0;
21
+ padding: 0;
22
+ }
23
+
24
+ dt {
25
+ width: 80px;
26
+ padding: 1px;
27
+ float: left;
28
+ clear: left;
29
+ text-align: right;
30
+ color: #7f7f7f;
31
+ }
32
+
33
+ dd {
34
+ margin-left: 90px; /* 80px + 10px */
35
+ padding: 1px;
36
+ }
37
+
38
+ dd:empty:before {
39
+ content: "\00a0"; // &nbsp;
40
+ }
41
+
42
+ th {
43
+ font-weight: inherit;
44
+ color: #7f7f7f;
45
+ text-align: right;
46
+ white-space: nowrap;
47
+ }
48
+
49
+ iframe {
50
+ border: 0;
51
+ width: 100%;
52
+ }
53
+
54
+ nav {
55
+ padding: 8px;
56
+ }
57
+
@@ -0,0 +1,2 @@
1
+ class Mailbin::ApplicationController < ActionController::Base
2
+ end
@@ -0,0 +1,8 @@
1
+ module Mailbin
2
+ class ClearsController < ApplicationController
3
+ def destroy
4
+ Mailbin.destroy_all
5
+ redirect_to root_path
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,85 @@
1
+ module Mailbin
2
+ class MessagesController < ApplicationController
3
+ around_action :set_locale, only: [ :show ]
4
+
5
+ helper_method :attachment_url, :part_query, :locale_query
6
+
7
+ content_security_policy(false)
8
+
9
+ rescue_from Errno::ENOENT do
10
+ redirect_to root_path
11
+ end
12
+
13
+ def index
14
+ @emails = Mailbin.all
15
+ end
16
+
17
+ def show
18
+ @email = Mailbin.find(params[:id])
19
+ @attachments = attachments_for(@email).reject { |filename, attachment| attachment.inline? }
20
+ @inline_attachments = attachments_for(@email).select { |filename, attachment| attachment.inline? }
21
+
22
+ if params[:part]
23
+ part_type = Mime::Type.lookup(params[:part])
24
+
25
+ if part = find_part(part_type)
26
+ response.content_type = part_type
27
+ render plain: part.respond_to?(:decoded) ? part.decoded : part
28
+ else
29
+ raise AbstractController::ActionNotFound, "Email part '#{part_type}' not found in #{@preview.name}##{@email_action}"
30
+ end
31
+ else
32
+ @part = find_preferred_part(request.format, Mime[:html], Mime[:text])
33
+ end
34
+ end
35
+
36
+ def destroy
37
+ Mailbin.destroy(params[:id])
38
+ redirect_to root_path
39
+ end
40
+
41
+ private
42
+
43
+ def find_preferred_part(*formats) # :doc:
44
+ formats.each do |format|
45
+ if part = @email.find_first_mime_type(format)
46
+ return part
47
+ end
48
+ end
49
+
50
+ if formats.any? { |f| @email.mime_type == f }
51
+ @email
52
+ end
53
+ end
54
+
55
+ def find_part(format) # :doc:
56
+ if part = @email.find_first_mime_type(format)
57
+ part
58
+ elsif @email.mime_type == format
59
+ @email
60
+ end
61
+ end
62
+
63
+ def attachments_for(email)
64
+ email.all_parts.to_a.select(&:attachment?).index_by do |attachment|
65
+ attachment.respond_to?(:original_filename) ? attachment.original_filename : attachment.filename
66
+ end
67
+ end
68
+
69
+ def attachment_url(attachment)
70
+ "data:application/octet-stream;charset=utf-8;base64,#{Base64.encode64(attachment.body.to_s)}"
71
+ end
72
+
73
+ def part_query(mime_type)
74
+ request.query_parameters.merge(part: mime_type).to_query
75
+ end
76
+
77
+ def locale_query(locale)
78
+ request.query_parameters.merge(locale: locale).to_query
79
+ end
80
+
81
+ def set_locale(&block)
82
+ I18n.with_locale(params[:locale] || I18n.default_locale, &block)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1 @@
1
+ import "@hotwired/turbo-rails"
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Mailbin</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <meta name="viewport" content="width=device-width,initial-scale=1">
9
+ <%= stylesheet_link_tag "mailbin/application", "data-turbo-track": "reload" %>
10
+ <%= javascript_importmap_tags "application-mailbin" %>
11
+ </head>
12
+ <body>
13
+ <%= yield %>
14
+ </body>
15
+ </html>
@@ -0,0 +1,14 @@
1
+ <%= link_to "Back to app", main_app.root_path if main_app.respond_to?(:root_path) %>
2
+
3
+ <h1>Sent Emails</h1>
4
+ <%= button_to "Clear all", clear_path, method: :delete, data: {turbo_confirm: "Are you sure?"} %>
5
+
6
+ <% if @emails.any? %>
7
+ <ul>
8
+ <% @emails.each do |email| %>
9
+ <li><%= link_to email.subject, message_path(email.message_id) %> - <%= l email.date, format: :long %></li>
10
+ <% end %>
11
+ </ul>
12
+ <% else %>
13
+ <p>You have not sent any emails.</p>
14
+ <% end %>
@@ -0,0 +1,143 @@
1
+ <nav>
2
+ <%= link_to "Back to emails", messages_path %>
3
+ </nav>
4
+
5
+ <header>
6
+ <dl>
7
+ <dt>Actions</dt>
8
+ <dd><%= button_to "Delete", message_path(@email.message_id), method: :delete, data: {turbo_confirm: "Are you sure?"} %> </dd>
9
+
10
+ <% if Array(@email.from) != Array(@email.smtp_envelope_from) %>
11
+ <dt>SMTP-From:</dt>
12
+ <dd id="smtp_from"><%= @email.smtp_envelope_from %></dd>
13
+ <% end %>
14
+
15
+ <% if Set[*@email.to, *@email.cc, *@email.bcc] != Set[*@email.smtp_envelope_to] %>
16
+ <dt>SMTP-To:</dt>
17
+ <dd id="smtp_to"><%= @email.smtp_envelope_to.join(", ") %></dd>
18
+ <% end %>
19
+
20
+ <dt>From:</dt>
21
+ <dd id="from"><%= @email.header['from'] %></dd>
22
+
23
+ <% if @email.reply_to %>
24
+ <dt>Reply-To:</dt>
25
+ <dd id="reply_to"><%= @email.header['reply-to'] %></dd>
26
+ <% end %>
27
+
28
+ <dt>To:</dt>
29
+ <dd id="to"><%= @email.header['to'] %></dd>
30
+
31
+ <% if @email.cc %>
32
+ <dt>CC:</dt>
33
+ <dd id="cc"><%= @email.header['cc'] %></dd>
34
+ <% end %>
35
+
36
+ <% if @email.bcc %>
37
+ <dt>BCC:</dt>
38
+ <dd id="bcc"><%= @email.header['bcc'] %></dd>
39
+ <% end %>
40
+
41
+ <dt>Date:</dt>
42
+ <dd id="date"><%= @email.header['date'] || Time.current.rfc2822 %></dd>
43
+
44
+ <dt>Subject:</dt>
45
+ <dd><strong id="subject"><%= @email.subject %></strong></dd>
46
+
47
+ <% if @attachments.any? || @inline_attachments.any? %>
48
+ <dt>Attachments:</dt>
49
+ <dd>
50
+ <% @attachments.each do |filename, attachment| %>
51
+ <%= link_to filename, attachment_url(attachment), download: filename %>
52
+ <% end %>
53
+
54
+ <% if @inline_attachments.any? %>
55
+ (Inline: <% @inline_attachments.each do |filename, attachment| %>
56
+ <%= link_to filename, attachment_url(attachment), download: filename %><% end %>)
57
+ <% end %>
58
+ </dd>
59
+ <% end %>
60
+
61
+ <dt>Format:</dt>
62
+ <% if @email.html_part && @email.text_part %>
63
+ <dd>
64
+ <select id="part" onchange="refreshBody(false);">
65
+ <option <%= request.format == Mime[:html] ? 'selected' : '' %> value="<%= part_query('text/html') %>">View as HTML email</option>
66
+ <option <%= request.format == Mime[:text] ? 'selected' : '' %> value="<%= part_query('text/plain') %>">View as plain-text email</option>
67
+ </select>
68
+ </dd>
69
+ <% elsif @part %>
70
+ <dd id="mime_type" data-mime-type="<%= part_query(@part.mime_type) %>"><%= @part.mime_type == 'text/html' ? 'HTML email' : 'plain-text email' %></dd>
71
+ <% else %>
72
+ <dd id="mime_type" data-mime-type=""></dd>
73
+ <% end %>
74
+
75
+ <% if I18n.available_locales.count > 1 %>
76
+ <dt>Locale:</dt>
77
+ <dd>
78
+ <select id="locale" onchange="refreshBody(true);">
79
+ <% I18n.available_locales.each do |locale| %>
80
+ <option <%= I18n.locale == locale ? 'selected' : '' %> value="<%= locale_query(locale) %>"><%= locale %></option>
81
+ <% end %>
82
+ </select>
83
+ </dd>
84
+ <% end %>
85
+
86
+ <% unless @email.header_fields.nil? || @email.header_fields.empty? %>
87
+ <dt>Headers:</dt>
88
+ <dd>
89
+ <details>
90
+ <summary>Show all headers</summary>
91
+ <table>
92
+ <% @email.header_fields.each do |field| %>
93
+ <tr>
94
+ <th><%= field.name %>:</th>
95
+ <td><%= field.value %></td>
96
+ </tr>
97
+ <% end %>
98
+ </table>
99
+ </details>
100
+ </dd>
101
+ <% end %>
102
+
103
+ <dt>EML File:</dt>
104
+ <dd><%= Mailbin.location_for(params[:id]) %></dd>
105
+ </dl>
106
+ </header>
107
+
108
+ <% if @part && @part.mime_type %>
109
+ <iframe name="messageBody" src="?<%= part_query(@part.mime_type) %>"></iframe>
110
+ <% else %>
111
+ <p>
112
+ You are trying to preview an email that does not have any content.
113
+ </p>
114
+ <% end %>
115
+
116
+ <script>
117
+ function refreshBody(reload) {
118
+ var part_select = document.querySelector('select#part');
119
+ var locale_select = document.querySelector('select#locale');
120
+ var iframe = document.getElementsByName('messageBody')[0];
121
+ var part_param = part_select ?
122
+ part_select.options[part_select.selectedIndex].value :
123
+ document.querySelector('#mime_type').dataset.mimeType;
124
+ var locale_param = locale_select ? locale_select.options[locale_select.selectedIndex].value : null;
125
+ var fresh_location;
126
+ if (locale_param) {
127
+ fresh_location = '?' + part_param + '&' + locale_param;
128
+ } else {
129
+ fresh_location = '?' + part_param;
130
+ }
131
+ iframe.contentWindow.location = fresh_location;
132
+
133
+ var url = location.pathname.replace(/\.(txt|html)$/, '');
134
+ var format = /html/.test(part_param) ? '.html' : '.txt';
135
+ var state_to_replace = locale_param ? (url + format + '?' + locale_param) : (url + format);
136
+
137
+ if (reload) {
138
+ location.href = state_to_replace;
139
+ } else if (history.replaceState) {
140
+ window.history.replaceState({}, '', state_to_replace);
141
+ }
142
+ }
143
+ </script>
@@ -0,0 +1,2 @@
1
+ pin "application-mailbin", to: "mailbin/application.js", preload: true
2
+ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ Mailbin::Engine.routes.draw do
2
+ resources :messages do
3
+ collection do
4
+ resource :clear
5
+ end
6
+ end
7
+
8
+ root to: "messages#index"
9
+ end
@@ -0,0 +1,24 @@
1
+ module Mailbin
2
+ class DeliveryMethod
3
+ attr_accessor :settings
4
+
5
+ def initialize(options = {})
6
+ self.settings = options
7
+ end
8
+
9
+ def deliver!(mail)
10
+ mail.message_id = SecureRandom.uuid
11
+
12
+ FileUtils.mkdir_p(settings[:location])
13
+ File.open(location_for(mail), "w") do |file|
14
+ file.write(mail.encoded)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def location_for(mail)
21
+ File.join(settings[:location], mail.message_id + ".eml")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ require "importmap-rails"
2
+ require "turbo-rails"
3
+
4
+ module Mailbin
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace Mailbin
7
+
8
+ initializer "mailbin.add_delivery_method" do
9
+ ActiveSupport.on_load :action_mailer do
10
+ ActionMailer::Base.add_delivery_method(
11
+ :mailbin,
12
+ Mailbin::DeliveryMethod,
13
+ location: Rails.root.join("tmp", "mailbin")
14
+ )
15
+ end
16
+ end
17
+
18
+ initializer "mailbin.assets" do |app|
19
+ app.config.assets.paths << root.join("app/javascript")
20
+ app.config.assets.precompile += %w[ mailbin_manifest ]
21
+ end
22
+
23
+ initializer "mailbin.importmap", before: "importmap" do |app|
24
+ app.config.importmap.paths << Engine.root.join("config/importmap.rb")
25
+ app.config.importmap.cache_sweepers << Engine.root.join("app/javascript")
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ module Mailbin
2
+ class InlinePreviewInterceptor < ActionMailer::InlinePreviewInterceptor
3
+ private
4
+ # Convert to base64 unless it's already done
5
+ def data_url(part)
6
+ source = part.body.encoding == "base64" ? part.body.raw_source : strict_encode64(part.body.raw_source)
7
+ "data:#{part.mime_type};base64,#{source}"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Mailbin
2
+ VERSION = "0.1.0"
3
+ end
data/lib/mailbin.rb ADDED
@@ -0,0 +1,38 @@
1
+ require "mailbin/version"
2
+ require "mailbin/engine"
3
+
4
+ module Mailbin
5
+ autoload :DeliveryMethod, "mailbin/delivery_method"
6
+ autoload :InlinePreviewInterceptor, "mailbin/inline_preview_interceptor"
7
+
8
+ class << self
9
+ def all
10
+ Dir.glob("*.eml", base: settings[:location]).map do |message_id|
11
+ find(message_id)
12
+ end.sort_by(&:date).reverse!
13
+ end
14
+
15
+ def find(message_id)
16
+ InlinePreviewInterceptor.previewing_email Mail.read(location_for(message_id))
17
+ end
18
+
19
+ def destroy(message_id)
20
+ File.delete location_for(message_id)
21
+ end
22
+
23
+ def destroy_all
24
+ Dir.glob("*.eml", base: settings[:location]).map do |message_id|
25
+ destroy(message_id)
26
+ end
27
+ end
28
+
29
+ def location_for(message_id)
30
+ message_id += ".eml" unless message_id.end_with?(".eml")
31
+ File.join(settings[:location], message_id)
32
+ end
33
+
34
+ def settings
35
+ ActionMailer::Base.mailbin_settings
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mailbin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Oliver
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 7.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 7.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: importmap-rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: turbo-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Mailbin collects emails from Rails ActionMailer in development for testing.
56
+ email:
57
+ - excid3@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - app/assets/config/mailbin_manifest.js
66
+ - app/assets/stylesheets/mailbin/application.css
67
+ - app/controllers/mailbin/application_controller.rb
68
+ - app/controllers/mailbin/clears_controller.rb
69
+ - app/controllers/mailbin/messages_controller.rb
70
+ - app/javascript/mailbin/application.js
71
+ - app/views/layouts/mailbin/application.html.erb
72
+ - app/views/mailbin/messages/index.html.erb
73
+ - app/views/mailbin/messages/show.html.erb
74
+ - config/importmap.rb
75
+ - config/routes.rb
76
+ - lib/mailbin.rb
77
+ - lib/mailbin/delivery_method.rb
78
+ - lib/mailbin/engine.rb
79
+ - lib/mailbin/inline_preview_interceptor.rb
80
+ - lib/mailbin/version.rb
81
+ homepage: https://github.com
82
+ licenses:
83
+ - MIT
84
+ metadata:
85
+ homepage_uri: https://github.com
86
+ source_code_uri: https://github.com
87
+ changelog_uri: https://github.com
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubygems_version: 3.5.16
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: Mailbin collects emails from Rails ActionMailer in development for testing.
107
+ test_files: []