rails-mailer-sandbox 0.2.0 → 0.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b3769b564df7ce2bf2278546029a59a92ad70ceec2e074ab4550abecbd4055e
4
- data.tar.gz: ac6f6d14a1e60deadfc5388c42426d0d79e942fe3ba65986199391589c4be47d
3
+ metadata.gz: bf80512fe3b9cd60f5842611ee2a68be8b1f5da0f7198872f5aaf0e62fc9c46a
4
+ data.tar.gz: 8436ff5fa82cf18b5dcb6ea7a905264fcf4bcb638aa711a2f0808528c0aee986
5
5
  SHA512:
6
- metadata.gz: 8744e34ffdf4cade29d895b9279c1f38aefd55e0beea7d50ef74387d5418b278c011116b9b16c1ccc70bb15cc7840bb8e5d0aad2e8ae51c8dff8e2aca3eea761
7
- data.tar.gz: ba697866c07972be8adafcae6de50690cd0c653d3f44f24583ab0e77587070ce84e740f1ce3f6f4777aa55f2b531d152819c0c5032c9f766f0d83dbce67cffe3
6
+ metadata.gz: 755b5e8a9368bd55e3c3221494a95aedef5604da4378c32ec62baa84a9754318c5a586a7dc4dd28a4b7a6fb6e1e27409613ed8059d3affba7398f4c6b511d234
7
+ data.tar.gz: b2a089a3a3b475457052833aac2ba039cd76a7ff39fd25d2bf4aac0c573ebd82cd3e6d7440ec2ae18e77bcd4848964092c604fbb1794bdf4ad88a582cd0b70d2
data/.yardopts ADDED
@@ -0,0 +1,8 @@
1
+ --markup markdown
2
+ --readme README.md
3
+ lib/**/*.rb
4
+ -
5
+ README.md
6
+ CHANGELOG.md
7
+ CONTRIBUTING.md
8
+ MIT-LICENSE
data/CHANGELOG.md ADDED
@@ -0,0 +1,55 @@
1
+ # Changelog
2
+
3
+ All notable changes to Rails Mailer Sandbox are documented here.
4
+
5
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions
6
+ follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.2] - 2026-09-21
9
+
10
+ ### Added
11
+
12
+ - Rubydoc.info documentation metadata and YARD annotations
13
+ - Documentation status badge in README
14
+ - `.yardopts` configuration file
15
+
16
+ ## [0.2.1] - 2026-09-13
17
+
18
+ ### Added
19
+
20
+ - Inline MIME image rendering through embedded `data:` URLs
21
+ - Safe new-tab behavior for links inside HTML previews
22
+ - RFC 5987 UTF-8 attachment download filenames
23
+ - Lightweight `/status.json` endpoint for inbox polling
24
+ - Recipient labels for Bcc-only and undisclosed-recipient messages
25
+ - Root `Gemfile` and GitHub Actions CI for Ruby 3.0 through 3.3
26
+ - Explicit `base64` dependency for Ruby 3.4 compatibility
27
+ - Regression tests for complex HTML-only and multipart emails
28
+
29
+ ### Fixed
30
+
31
+ - CSS and script content leaking into generated plain-text previews
32
+ - Search ignoring body content after first 140 characters
33
+ - Inline `cid:` images appearing broken
34
+ - Links without `target="_blank"` doing nothing inside preview
35
+ - Every polling request parsing all stored `.eml` files
36
+
37
+ ### Security
38
+
39
+ - Preview links escape sandbox only into new tabs; scripts, forms, remote resources,
40
+ and top-level navigation remain blocked
41
+
42
+ ## [0.2.0] - 2026-09-13
43
+
44
+ ### Added
45
+
46
+ - Initial RubyGems release
47
+ - Action Mailer delivery method
48
+ - Persistent filesystem-backed `.eml` storage
49
+ - Mountable Rails Web UI with search, HTML/text/raw views, responsive preview widths,
50
+ attachments, automatic refresh, and message deletion
51
+ - `rails_mailer_sandbox:setup` Rake task
52
+
53
+ [0.2.2]: https://github.com/azmi2409/rails-mailer-sandbox/compare/v0.2.1...v0.2.2
54
+ [0.2.1]: https://github.com/azmi2409/rails-mailer-sandbox/compare/v0.2.0...v0.2.1
55
+ [0.2.0]: https://github.com/azmi2409/rails-mailer-sandbox/releases/tag/v0.2.0
data/README.md CHANGED
@@ -1,47 +1,55 @@
1
- # Rails Mailer Sandbox
1
+ # Rails Mailer Sandbox 📬
2
2
 
3
- Development email sandbox for Rails. Captures Action Mailer deliveries, stores original
4
- `.eml` messages on disk, and exposes a Mailtrap-inspired Web UI inside your Rails app.
3
+ [![CI](https://github.com/azmi2409/rails-mailer-sandbox/actions/workflows/ci.yml/badge.svg)](https://github.com/azmi2409/rails-mailer-sandbox/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/rails-mailer-sandbox.svg)](https://rubygems.org/gems/rails-mailer-sandbox)
5
+ [![Documentation](https://img.shields.io/badge/docs-rubydoc.info-blue.svg)](https://rubydoc.info/gems/rails-mailer-sandbox)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](MIT-LICENSE)
5
7
 
6
- ## Features
8
+ Catch Rails emails locally before they escape into the wild. Rails Mailer Sandbox stores
9
+ real `.eml` messages and gives you a Mailtrap-inspired inbox right inside your Rails app.
10
+ No SMTP server, database, account, or monthly bill needed.
7
11
 
8
- - Persistent MIME message storage across Rails restarts
9
- - Search by subject, sender, recipient, or body preview
12
+ ## ✨ What You Get
13
+
14
+ - Persistent MIME messages across Rails restarts
15
+ - Search by subject, sender, recipient, or full body
10
16
  - HTML, plain-text, and raw-source views
11
17
  - Desktop, tablet, and mobile preview widths
12
18
  - From, To, Cc, Bcc, date, and message-size details
13
- - Attachment downloads
14
- - Automatic inbox refresh
19
+ - Inline `cid:` image previews and attachment downloads
20
+ - UTF-8 attachment filenames
21
+ - Automatic inbox refresh without repeatedly parsing every email
15
22
  - Individual and bulk deletion
16
- - Sandboxed HTML previews with remote resources and scripts blocked
23
+ - Sandboxed HTML previews with scripts and remote resources blocked
17
24
  - Filesystem locking and atomic writes for Rails and background workers
18
- - No database or external service
19
25
 
20
- ## Installation
26
+ ## 🚀 Get Started
21
27
 
22
28
  Add gem to development group:
23
29
 
24
30
  ```ruby
25
31
  group :development do
26
- gem "rails-mailer-sandbox", github: "azmi2409/rails-mailer-sandbox"
32
+ gem "rails-mailer-sandbox"
27
33
  end
28
34
  ```
29
35
 
30
- Then run:
36
+ Set it up:
31
37
 
32
38
  ```bash
33
39
  bundle install
34
40
  bundle exec rake rails_mailer_sandbox:setup
35
41
  ```
36
42
 
37
- Restart Rails and open <http://localhost:3000/rails_mailer_sandbox>.
43
+ Restart Rails, send any Action Mailer email, then visit:
44
+
45
+ 👉 <http://localhost:3000/rails_mailer_sandbox>
38
46
 
39
47
  Setup creates `config/initializers/rails_mailer_sandbox.rb`, configures Action Mailer,
40
- and mounts Web UI. Repeating task is safe. Existing edited initializer is never overwritten.
48
+ and mounts Web UI. Run task again safely; it will not overwrite edited initializer.
41
49
 
42
- ## Manual Setup
50
+ ## 🔧 Manual Setup
43
51
 
44
- Configure `config/environments/development.rb`:
52
+ Prefer doing it yourself? Add this to `config/environments/development.rb`:
45
53
 
46
54
  ```ruby
47
55
  config.action_mailer.delivery_method = :rails_mailer_sandbox
@@ -49,35 +57,47 @@ config.action_mailer.perform_deliveries = true
49
57
  config.action_mailer.raise_delivery_errors = true
50
58
  ```
51
59
 
52
- Mount Web UI in `config/routes.rb`:
60
+ Mount inbox in `config/routes.rb`:
53
61
 
54
62
  ```ruby
55
63
  mount RailsMailerSandbox::Server.new => "/rails_mailer_sandbox" if Rails.env.development?
56
64
  ```
57
65
 
58
- ## Storage
66
+ ## 💾 Where Emails Live
59
67
 
60
68
  Default path: `Rails.root/tmp/rails_mailer_sandbox`.
61
69
 
62
- Override path in initializer:
70
+ Want another location? Set it in initializer:
63
71
 
64
72
  ```ruby
65
73
  RailsMailerSandbox.storage_path = Rails.root.join("storage", "development_mail")
66
74
  ```
67
75
 
68
- Keep custom storage outside version control. Rails `tmp:clear` removes default inbox.
69
- Local filesystem storage suits development on one host. It does not target large archives
70
- or distributed deployments.
76
+ Keep custom storage out of version control. `bin/rails tmp:clear` removes default inbox.
77
+ Filesystem storage targets local development on one host, not distributed deployments or
78
+ giant email archives.
71
79
 
72
- ## Development
80
+ ## 🧪 Development
73
81
 
74
82
  ```bash
75
- ruby -Ilib test/rails_mailer_sandbox_test.rb
83
+ git clone https://github.com/azmi2409/rails-mailer-sandbox.git
84
+ cd rails-mailer-sandbox
85
+ bundle install
86
+ bundle exec ruby -Ilib test/rails_mailer_sandbox_test.rb
76
87
  gem build rails-mailer-sandbox.gemspec
77
88
  ```
78
89
 
79
- See [CONTRIBUTING.md](CONTRIBUTING.md) before opening pull request.
90
+ CI runs tests and gem build on Ruby 3.0, 3.1, 3.2, and 3.3.
91
+ Found a bug or have a neat focused improvement? Read [CONTRIBUTING.md](CONTRIBUTING.md),
92
+ then open an issue or pull request. 🙌
93
+
94
+ Release notes live in [CHANGELOG.md](CHANGELOG.md). 📝
95
+
96
+ ## 🔒 One Important Rule
97
+
98
+ Use this gem in development only. Captured emails may contain reset links, personal data,
99
+ or other secrets. Never expose mounted inbox publicly or enable it in production.
80
100
 
81
- ## License
101
+ ## 📄 License
82
102
 
83
- [MIT](MIT-LICENSE)
103
+ [MIT](MIT-LICENSE). Build useful things and test your emails. 🎉
@@ -3,7 +3,11 @@
3
3
  require "fileutils"
4
4
 
5
5
  module RailsMailerSandbox
6
+ # Generator for sandbox initializer.
6
7
  module Setup
8
+ # Generates initializer and configures routes.
9
+ # @param root [String, Pathname] Rails application root path
10
+ # @return [void]
7
11
  def self.run(root)
8
12
  root = File.expand_path(root)
9
13
  raise "Not a Rails application: #{root}" unless File.file?(File.join(root, "config/application.rb"))
@@ -8,22 +8,51 @@ require "time"
8
8
  require "date"
9
9
  require "fileutils"
10
10
  require "mail"
11
+ require "base64"
12
+ require "uri"
11
13
 
14
+ # Module for local Action Mailer email preview and storage sandbox.
12
15
  module RailsMailerSandbox
13
- VERSION = "0.2.0"
16
+ VERSION = "0.2.2"
14
17
 
15
18
  class << self
19
+ # @return [String, nil]
16
20
  attr_writer :storage_path
17
21
 
22
+ # Directory where captured emails are stored.
23
+ # @return [String]
18
24
  def storage_path
19
25
  @storage_path || File.join(defined?(Rails) && Rails.respond_to?(:root) && Rails.root ? Rails.root.to_s : Dir.pwd, "tmp", "rails_mailer_sandbox")
20
26
  end
21
27
  end
22
28
 
29
+ # Represents a captured email message.
23
30
  class Message
24
- attr_reader :id, :from, :to, :cc, :bcc, :subject, :date, :text_part,
25
- :html_part, :raw_source, :attachments
26
-
31
+ # @return [String] Unique message identifier
32
+ attr_reader :id
33
+ # @return [Array<String>] Sender email addresses
34
+ attr_reader :from
35
+ # @return [Array<String>] Recipient email addresses
36
+ attr_reader :to
37
+ # @return [Array<String>] CC recipient email addresses
38
+ attr_reader :cc
39
+ # @return [Array<String>] BCC recipient email addresses
40
+ attr_reader :bcc
41
+ # @return [String] Email subject line
42
+ attr_reader :subject
43
+ # @return [Time, DateTime] Message timestamp
44
+ attr_reader :date
45
+ # @return [String, nil] Plain text body
46
+ attr_reader :text_part
47
+ # @return [String, nil] HTML body
48
+ attr_reader :html_part
49
+ # @return [String] Raw MIME message source
50
+ attr_reader :raw_source
51
+ # @return [Array<Hash{Symbol => Object}>] Parsed attachments
52
+ attr_reader :attachments
53
+
54
+ # @param mail [Mail::Message]
55
+ # @param id [String]
27
56
  def initialize(mail, id: SecureRandom.uuid)
28
57
  @id = id
29
58
  @date = mail.date || Time.now
@@ -38,46 +67,74 @@ module RailsMailerSandbox
38
67
  @raw_source = "Bcc: #{@bcc.join(', ').gsub(/[\r\n]/, '')}\r\n#{@raw_source}" unless @bcc.empty? || @raw_source.match?(/^Bcc:/i)
39
68
  end
40
69
 
70
+ # @return [Array<String>]
41
71
  def recipients
42
72
  to + cc + bcc
43
73
  end
44
74
 
75
+ # @return [String]
45
76
  def preview
46
77
  text_part.to_s.gsub(/\s+/, " ").strip[0, 140]
47
78
  end
48
79
 
80
+ # @return [Integer]
49
81
  def size
50
82
  raw_source.bytesize
51
83
  end
52
84
 
85
+ # @param query [String]
86
+ # @return [Boolean]
53
87
  def matches?(query)
54
- query.empty? || [subject, *from, *recipients, preview].any? { |value| value.downcase.include?(query) }
88
+ query.empty? || [subject, *from, *recipients, text_part.to_s].any? { |value| value.downcase.include?(query) }
55
89
  end
56
90
 
57
91
  private
58
92
 
59
93
  def extract_parts(mail)
60
94
  parts = mail.multipart? ? mail.all_parts : [mail]
95
+ inline_parts = {}
61
96
  parts.each do |part|
62
97
  if part.respond_to?(:attachment?) && part.attachment?
98
+ content_id = part.content_id.to_s.delete("<>")
63
99
  @attachments << {
64
100
  filename: part.filename.to_s,
65
101
  content_type: part.mime_type.to_s,
66
102
  body: part.body.decoded
67
103
  }
104
+ inline_parts[content_id] = part unless content_id.empty?
105
+ inline_parts[part.filename.to_s] = part unless part.filename.to_s.empty?
68
106
  elsif part.mime_type == "text/html" && !@html_part
69
107
  @html_part = part.body.decoded
70
108
  elsif part.mime_type == "text/plain" && !@text_part
71
109
  @text_part = part.body.decoded
72
110
  end
73
111
  end
74
- @text_part ||= @html_part&.gsub(/<[^>]*>/, " ")&.gsub(/\s+/, " ")&.strip
112
+ inline_parts.each do |content_id, part|
113
+ data_uri = "data:#{part.mime_type};base64,#{Base64.strict_encode64(part.body.decoded)}"
114
+ @html_part&.gsub!(/cid:#{Regexp.escape(content_id)}/i, data_uri)
115
+ end
116
+ @html_part = with_external_link_target(@html_part) if @html_part
117
+ @text_part ||= @html_part&.gsub(/<style\b[^>]*>.*?<\/style>/im, " ")
118
+ &.gsub(/<script\b[^>]*>.*?<\/script>/im, " ")
119
+ &.gsub(/<[^>]*>/, " ")&.then { |text| CGI.unescapeHTML(text) }&.gsub(/\s+/, " ")&.strip
75
120
  @html_part ||= "<pre style=\"white-space:pre-wrap\">#{CGI.escapeHTML(@text_part.to_s)}</pre>"
76
121
  end
122
+
123
+ def with_external_link_target(html)
124
+ if html.match?(/<head\b[^>]*>/i)
125
+ html.sub(/<head\b[^>]*>/i) { |head| "#{head}<base target=\"_blank\">" }
126
+ else
127
+ "<base target=\"_blank\">#{html}"
128
+ end
129
+ end
77
130
  end
78
131
 
132
+ # Thread-safe filesystem storage for captured emails.
79
133
  class Storage
80
134
  class << self
135
+ # Stores an email message.
136
+ # @param mail [Mail::Message]
137
+ # @return [Message]
81
138
  def add(mail)
82
139
  message = Message.new(mail)
83
140
  locked do |directory|
@@ -96,6 +153,8 @@ module RailsMailerSandbox
96
153
  message
97
154
  end
98
155
 
156
+ # Returns all stored messages, ordered newest first.
157
+ # @return [Array<Message>]
99
158
  def all
100
159
  locked do |directory|
101
160
  Dir.glob(File.join(directory, "*.eml")).sort_by { |path| [File.mtime(path), path] }.reverse.map do |path|
@@ -104,6 +163,9 @@ module RailsMailerSandbox
104
163
  end
105
164
  end
106
165
 
166
+ # Finds message by UUID.
167
+ # @param id [String]
168
+ # @return [Message, nil]
107
169
  def find(id)
108
170
  return unless valid_id?(id)
109
171
  locked do |directory|
@@ -112,6 +174,9 @@ module RailsMailerSandbox
112
174
  end
113
175
  end
114
176
 
177
+ # Deletes message by UUID.
178
+ # @param id [String]
179
+ # @return [Boolean, nil]
115
180
  def delete(id)
116
181
  return unless valid_id?(id)
117
182
  locked do |directory|
@@ -120,10 +185,14 @@ module RailsMailerSandbox
120
185
  end
121
186
  end
122
187
 
188
+ # Clears all stored messages.
189
+ # @return [Array<String>]
123
190
  def clear
124
191
  locked { |directory| Dir.glob(File.join(directory, "*.eml")).each { |path| File.delete(path) } }
125
192
  end
126
193
 
194
+ # Returns count of stored messages.
195
+ # @return [Integer]
127
196
  def count
128
197
  locked { |directory| Dir.glob(File.join(directory, "*.eml")).size }
129
198
  end
@@ -148,18 +217,25 @@ module RailsMailerSandbox
148
217
  end
149
218
  end
150
219
 
220
+ # Action Mailer delivery method for sandbox storage.
151
221
  class DeliveryMethod
222
+ # @return [Hash]
152
223
  attr_accessor :settings
153
224
 
225
+ # @param values [Hash]
154
226
  def initialize(values = {})
155
227
  @settings = values
156
228
  end
157
229
 
230
+ # Stores incoming email.
231
+ # @param mail [Mail::Message]
232
+ # @return [Message]
158
233
  def deliver!(mail)
159
234
  Storage.add(mail)
160
235
  end
161
236
  end
162
237
 
238
+ # Rack application providing Web UI to inspect captured emails.
163
239
  class Server
164
240
  SECURITY_HEADERS = {
165
241
  "content-security-policy" => "default-src 'self'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; frame-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; frame-ancestors 'self'",
@@ -168,6 +244,9 @@ module RailsMailerSandbox
168
244
  "x-frame-options" => "SAMEORIGIN"
169
245
  }.freeze
170
246
 
247
+ # Rack call entrypoint.
248
+ # @param env [Hash]
249
+ # @return [Array]
171
250
  def call(env)
172
251
  request = Rack::Request.new(env)
173
252
  response = route(request)
@@ -189,7 +268,7 @@ module RailsMailerSandbox
189
268
  end
190
269
 
191
270
  return render_index(request) if method == "GET" && ["", "/"].include?(path)
192
- return render_messages_json if method == "GET" && path == "/messages.json"
271
+ return render_status_json if method == "GET" && path == "/status.json"
193
272
  return render_part(Regexp.last_match(1), :html_part, "text/html; charset=utf-8") if method == "GET" && path =~ %r{\A/messages/([^/]+)/html\z}
194
273
  return render_part(Regexp.last_match(1), :raw_source, "text/plain; charset=utf-8") if method == "GET" && path =~ %r{\A/messages/([^/]+)/raw\z}
195
274
  return render_attachment(Regexp.last_match(1), Regexp.last_match(2).to_i) if method == "GET" && path =~ %r{\A/messages/([^/]+)/attachment/(\d+)\z}
@@ -209,7 +288,7 @@ module RailsMailerSandbox
209
288
  return not_found unless message
210
289
 
211
290
  headers = { "content-type" => content_type }
212
- headers["content-security-policy"] = "sandbox; default-src 'none'; img-src data:; style-src 'unsafe-inline'; font-src data:; base-uri 'none'; form-action 'none'" if field == :html_part
291
+ headers["content-security-policy"] = "sandbox allow-popups allow-popups-to-escape-sandbox; default-src 'none'; img-src data:; style-src 'unsafe-inline'; font-src data:; base-uri 'self'; form-action 'none'" if field == :html_part
213
292
  [200, headers, [message.public_send(field).to_s]]
214
293
  end
215
294
 
@@ -217,20 +296,19 @@ module RailsMailerSandbox
217
296
  attachment = Storage.find(id)&.attachments&.[](index)
218
297
  return not_found unless attachment
219
298
 
220
- filename = attachment[:filename].encode("US-ASCII", invalid: :replace, undef: :replace).gsub(/[\x00-\x1f\x7f"\\]/, "_")
299
+ original_filename = attachment[:filename].encode("UTF-8", invalid: :replace, undef: :replace).gsub(/[\x00-\x1f\x7f]/, "_")
300
+ filename = original_filename.encode("US-ASCII", invalid: :replace, undef: :replace, replace: "_").gsub(/["\\]/, "_")
301
+ encoded_filename = URI.encode_www_form_component(original_filename).tr("+", "%20")
221
302
  type = attachment[:content_type].match?(/\A[-\w.+]+\/[-\w.+]+\z/) ? attachment[:content_type] : "application/octet-stream"
222
303
  [200, {
223
304
  "content-type" => type,
224
- "content-disposition" => %(attachment; filename="#{filename}"),
305
+ "content-disposition" => %(attachment; filename="#{filename}"; filename*=UTF-8''#{encoded_filename}),
225
306
  "content-length" => attachment[:body].bytesize.to_s
226
307
  }, [attachment[:body]]]
227
308
  end
228
309
 
229
- def render_messages_json
230
- payload = Storage.all.map do |message|
231
- { id: message.id, subject: message.subject, from: message.from, to: message.to, date: message.date.iso8601 }
232
- end
233
- [200, { "content-type" => "application/json; charset=utf-8", "cache-control" => "no-store" }, [JSON.generate(payload)]]
310
+ def render_status_json
311
+ [200, { "content-type" => "application/json; charset=utf-8", "cache-control" => "no-store" }, [JSON.generate(count: Storage.count)]]
234
312
  end
235
313
 
236
314
  def render_index(request)
@@ -286,6 +364,8 @@ module RailsMailerSandbox
286
364
  messages.map do |message|
287
365
  params = "?id=#{Rack::Utils.escape(message.id)}"
288
366
  params += "&q=#{Rack::Utils.escape(query)}" unless query.empty?
367
+ recipient = message.to.first || message.recipients.first
368
+ recipient_label = recipient ? "to #{recipient}" : "undisclosed recipients"
289
369
  <<~HTML
290
370
  <a class="message #{"active" if message == selected}" href="#{base}#{params}">
291
371
  <span class="avatar">#{h(initials(message.from.first))}</span>
@@ -293,7 +373,7 @@ module RailsMailerSandbox
293
373
  <span class="message-line"><strong>#{h(message.from.first || "Unknown sender")}</strong><time>#{h(short_time(message.date))}</time></span>
294
374
  <span class="subject">#{h(message.subject.empty? ? "(no subject)" : message.subject)}</span>
295
375
  <span class="preview">#{h(message.preview)}</span>
296
- <span class="recipient">to #{h(message.to.join(", "))}</span>
376
+ <span class="recipient">#{h(recipient_label)}</span>
297
377
  </span>
298
378
  </a>
299
379
  HTML
@@ -332,7 +412,7 @@ module RailsMailerSandbox
332
412
  <button class="viewport" data-width="390px" title="Mobile preview">▯</button>
333
413
  </nav>
334
414
  <section class="preview-area">
335
- <div class="pane active" id="pane-html"><iframe title="Email HTML preview" sandbox="allow-popups" src="#{base}/messages/#{message.id}/html"></iframe></div>
415
+ <div class="pane active" id="pane-html"><iframe title="Email HTML preview" sandbox="allow-popups allow-popups-to-escape-sandbox" src="#{base}/messages/#{message.id}/html"></iframe></div>
336
416
  <pre class="pane source" id="pane-text">#{h(message.text_part.to_s)}</pre>
337
417
  <pre class="pane source" id="pane-raw">#{h(message.raw_source.to_s)}</pre>
338
418
  </section>
@@ -405,9 +485,9 @@ module RailsMailerSandbox
405
485
  setInterval(async () => {
406
486
  if (document.querySelector('input[name=q]').value) return;
407
487
  try {
408
- const response = await fetch('#{base}/messages.json', { headers: { accept: 'application/json' } });
409
- const messages = await response.json();
410
- if (messages.length !== Number(document.body.dataset.count)) location.reload();
488
+ const response = await fetch('#{base}/status.json', { headers: { accept: 'application/json' } });
489
+ const status = await response.json();
490
+ if (status.count !== Number(document.body.dataset.count)) location.reload();
411
491
  } catch (_) {}
412
492
  }, 3000);
413
493
  JS
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-mailer-sandbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - azmimuwahid
@@ -37,6 +37,34 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '2.8'
40
+ - !ruby/object:Gem::Dependency
41
+ name: base64
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.2'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.2'
54
+ - !ruby/object:Gem::Dependency
55
+ name: test-unit
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
40
68
  description: Intercepts ActionMailer emails in development and provides mountable
41
69
  Web UI to preview them.
42
70
  email:
@@ -45,6 +73,8 @@ executables: []
45
73
  extensions: []
46
74
  extra_rdoc_files: []
47
75
  files:
76
+ - ".yardopts"
77
+ - CHANGELOG.md
48
78
  - CONTRIBUTING.md
49
79
  - MIT-LICENSE
50
80
  - README.md
@@ -55,7 +85,10 @@ files:
55
85
  homepage: https://github.com/azmi2409/rails-mailer-sandbox
56
86
  licenses:
57
87
  - MIT
58
- metadata: {}
88
+ metadata:
89
+ documentation_uri: https://rubydoc.info/gems/rails-mailer-sandbox
90
+ source_code_uri: https://github.com/azmi2409/rails-mailer-sandbox
91
+ changelog_uri: https://github.com/azmi2409/rails-mailer-sandbox/blob/main/CHANGELOG.md
59
92
  rdoc_options: []
60
93
  require_paths:
61
94
  - lib