mailcatcher 0.6.5 → 0.8.0.beta3

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
- SHA1:
3
- metadata.gz: db48fc085519aaa785c4b09bd3fa312d25ab6e6f
4
- data.tar.gz: 639ee7311ee33602a6e2ab8d5552e662dfe48199
2
+ SHA256:
3
+ metadata.gz: 1fbbb01ee21a1d0946b2a6ba1e52b999567a8894b8f6b8816f4d796d924b4967
4
+ data.tar.gz: 6d6cc1c27dd58ce096fe1458075ce2ae04a30a22abba3629cc1ddbc9c7f3a7f4
5
5
  SHA512:
6
- metadata.gz: 1b5c917befe705b2937ca462b1eef9d8b8e11f5d8c6a725e0192925ca2d2b2d52ebfe168e5560421af091ba4aeca5884e117b977a893219780c8eecddcbe25b2
7
- data.tar.gz: 14fe6761586cc46b741f4bc4c8b893caf35767afeb67a1f99a3b62e86c94c162127b536f482860c992d690af15d8ba5e367e5dfdfdd0f6086ab7000c36632eff
6
+ metadata.gz: dcd37866fb64d92ebceaecb15747d68db22eed327417d6025db91c88b6ab5bdeaeff96b9596e2d011610a61da74deb8bbdd363a35a9da8dd75e2f1e7469b411f
7
+ data.tar.gz: 2057962471f539be4e496243285efabe3c31f9b24eca7c560e9645f32401839f481c5da116f9df6e7a407cd6e1e0cacefd7c03f14a00a8e798cdc23f8c7e0b86
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -10,44 +10,61 @@ MailCatcher runs a super simple SMTP server which catches any message sent to it
10
10
 
11
11
  * Catches all mail and stores it for display.
12
12
  * Shows HTML, Plain Text and Source version of messages, as applicable.
13
- * Rewrites HTML enabling display of embedded, inline images/etc and open links in a new window.
13
+ * Rewrites HTML enabling display of embedded, inline images/etc and opens links in a new window.
14
14
  * Lists attachments and allows separate downloading of parts.
15
15
  * Download original email to view in your native mail client(s).
16
16
  * Command line options to override the default SMTP/HTTP IP and port settings.
17
17
  * Mail appears instantly if your browser supports [WebSockets][websockets], otherwise updates every thirty seconds.
18
- * Runs as a daemon run in the background.
19
- * Sendmail-analogue command, `catchmail`, makes [using mailcatcher from PHP][withphp] a lot easier.
20
- * Written super-simply in EventMachine, easy to dig in and change.
18
+ * Runs as a daemon in the background, optionally in foreground.
19
+ * Sendmail-analogue command, `catchmail`, makes using mailcatcher from PHP a lot easier.
21
20
  * Keyboard navigation between messages
22
21
 
23
22
  ## How
24
23
 
25
24
  1. `gem install mailcatcher`
26
25
  2. `mailcatcher`
27
- 3. Go to http://localhost:1080/
28
- 4. Send mail through smtp://localhost:1025
26
+ 3. Go to http://127.0.0.1:1080/
27
+ 4. Send mail through smtp://127.0.0.1:1025
29
28
 
30
29
  Use `mailcatcher --help` to see the command line options. The brave can get the source from [the GitHub repository][mailcatcher-github].
31
30
 
31
+ ### Ruby
32
+
33
+ If you have trouble with the above commands, make sure you have [Ruby installed](https://www.ruby-lang.org/en/documentation/installation/):
34
+
35
+ ```
36
+ ruby -v
37
+ gem environment
38
+ ```
39
+
40
+ You might need to install build tools for some of the gem dependencies. On Debian or Ubuntu, `apt install build-essential`. On macOS, `xcode-select --install`.
41
+
42
+ If you encounter issues installing [thin](https://rubygems.org/gems/thin), try:
43
+
44
+ ```
45
+ gem install thin -v 1.5.1 -- --with-cflags="-Wno-error=implicit-function-declaration"
46
+ ```
47
+
32
48
  ### Bundler
33
49
 
34
50
  Please don't put mailcatcher into your Gemfile. It will conflict with your applications gems at some point.
35
51
 
36
- Instead, pop a note in your README stating you use mailcatcher. Simply run `gem install mailcatcher` then `mailcatcher` to get started.
52
+ Instead, pop a note in your README stating you use mailcatcher, and to run `gem install mailcatcher` then `mailcatcher` to get started.
37
53
 
38
54
  ### RVM
39
55
 
40
- Under RVM your mailcatcher command may only be available under the ruby you install mailcatcher into. To prevent this, and to prevent gem conflicts, install mailcatcher into a dedicated gemset and create wrapper scripts:
56
+ Under RVM your mailcatcher command may only be available under the ruby you install mailcatcher into. To prevent this, and to prevent gem conflicts, install mailcatcher into a dedicated gemset with a wrapper script:
41
57
 
42
58
  rvm default@mailcatcher --create do gem install mailcatcher
43
- rvm wrapper default@mailcatcher --no-prefix mailcatcher catchmail
59
+ ln -s "$(rvm default@mailcatcher do rvm wrapper show mailcatcher)" "$rvm_bin_path/"
44
60
 
45
61
  ### Rails
46
62
 
47
63
  To set up your rails app, I recommend adding this to your `environments/development.rb`:
48
64
 
49
65
  config.action_mailer.delivery_method = :smtp
50
- config.action_mailer.smtp_settings = { :address => "localhost", :port => 1025 }
66
+ config.action_mailer.smtp_settings = { :address => '127.0.0.1', :port => 1025 }
67
+ config.action_mailer.raise_delivery_errors = false
51
68
 
52
69
  ### PHP
53
70
 
@@ -67,7 +84,7 @@ If starting `mailcatcher` on alternative SMTP IP and/or port with parameters lik
67
84
 
68
85
  ### Django
69
86
 
70
- For use in Django, simply add the following configuration to your projects' settings.py
87
+ For use in Django, add the following configuration to your projects' settings.py
71
88
 
72
89
  ```python
73
90
  if DEBUG:
@@ -85,36 +102,22 @@ A fairly RESTful URL schema means you can download a list of messages in JSON fr
85
102
  ## Caveats
86
103
 
87
104
  * Mail processing is fairly basic but easily modified. If something doesn't work for you, fork and fix it or [file an issue][mailcatcher-issues] and let me know. Include the whole message you're having problems with.
88
- * The interface is very basic and has not been tested on many browsers yet.
89
-
90
- ## TODO
91
-
92
- * Add mail delivery on request, optionally multiple times.
93
- * Compatibility testing against CampaignMonitor's [design guidelines](http://www.campaignmonitor.com/design-guidelines/) and [CSS support matrix](http://www.campaignmonitor.com/css/).
94
- * Forward mail to rendering service, maybe CampaignMonitor?
105
+ * Encodings are difficult. MailCatcher does not completely support utf-8 straight over the wire, you must use a mail library which encodes things properly based on SMTP server capabilities.
95
106
 
96
107
  ## Thanks
97
108
 
98
109
  MailCatcher is just a mishmash of other people's hard work. Thank you so much to the people who have built the wonderful guts on which this project relies.
99
110
 
100
- Thanks also to [The Frontier Group][tfg] for giving me the idea, being great guinea pigs and letting me steal pieces of time to keep the project alive.
101
-
102
111
  ## Donations
103
112
 
104
113
  I work on MailCatcher mostly in my own spare time. If you've found Mailcatcher useful and would like to help feed me and fund continued development and new features, please [donate via PayPal][donate]. If you'd like a specific feature added to MailCatcher and are willing to pay for it, please [email me](mailto:sj26@sj26.com).
105
114
 
106
115
  ## License
107
116
 
108
- Copyright © 2010-2011 Samuel Cochran (sj26@sj26.com). Released under the MIT License, see [LICENSE][license] for details.
109
-
110
- ## Dreams
111
-
112
- For dream catching, try [this](http://goo.gl/kgbh). OR [THIS](http://www.nyanicorn.com), OMG.
117
+ Copyright © 2010-2019 Samuel Cochran (sj26@sj26.com). Released under the MIT License, see [LICENSE][license] for details.
113
118
 
114
119
  [donate]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=522WUPLRWUSKE
115
120
  [license]: https://github.com/sj26/mailcatcher/blob/master/LICENSE
116
121
  [mailcatcher-github]: https://github.com/sj26/mailcatcher
117
122
  [mailcatcher-issues]: https://github.com/sj26/mailcatcher/issues
118
- [tfg]: http://www.thefrontiergroup.com.au
119
123
  [websockets]: http://www.whatwg.org/specs/web-socket-protocol/
120
- [withphp]: http://webschuur.com/publications/blogs/2011-05-29-catchmail_for_drupal_and_other_phpapplications_the_simple_version
data/bin/catchmail CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  begin
4
5
  require 'mail'
data/bin/mailcatcher CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'mail_catcher'
4
5
 
data/lib/mail_catcher.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Apparently rubygems won't activate these on its own, so here we go. Let's
2
4
  # repeat the invention of Bundler all over again.
3
5
  gem "eventmachine", "1.0.9.1"
@@ -23,13 +25,22 @@ module EventMachine
23
25
  end
24
26
  end
25
27
 
26
- require "mail_catcher/events"
27
- require "mail_catcher/mail"
28
- require "mail_catcher/smtp"
29
- require "mail_catcher/web"
30
28
  require "mail_catcher/version"
31
29
 
32
30
  module MailCatcher extend self
31
+ autoload :Bus, "mail_catcher/bus"
32
+ autoload :Mail, "mail_catcher/mail"
33
+ autoload :Smtp, "mail_catcher/smtp"
34
+ autoload :Web, "mail_catcher/web"
35
+
36
+ def env
37
+ ENV.fetch("MAILCATCHER_ENV", "production")
38
+ end
39
+
40
+ def development?
41
+ env == "development"
42
+ end
43
+
33
44
  def which?(command)
34
45
  ENV["PATH"].split(File::PATH_SEPARATOR).any? do |directory|
35
46
  File.executable?(File.join(directory, command.to_s))
@@ -37,11 +48,11 @@ module MailCatcher extend self
37
48
  end
38
49
 
39
50
  def mac?
40
- RbConfig::CONFIG['host_os'] =~ /darwin/
51
+ RbConfig::CONFIG["host_os"] =~ /darwin/
41
52
  end
42
53
 
43
54
  def windows?
44
- RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
55
+ RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
45
56
  end
46
57
 
47
58
  def macruby?
@@ -62,7 +73,7 @@ module MailCatcher extend self
62
73
 
63
74
  def log_exception(message, context, exception)
64
75
  gems_paths = (Gem.path | [Gem.default_dir]).map { |path| Regexp.escape(path) }
65
- gems_regexp = %r{(?:#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)}
76
+ gems_regexp = %r{(?:#{gems_paths.join("|")})/gems/([^/]+)-([\w.]+)/(.*)}
66
77
  gems_replace = '\1 (\2) \3'
67
78
 
68
79
  puts "*** #{message}: #{context.inspect}"
@@ -72,10 +83,12 @@ module MailCatcher extend self
72
83
  end
73
84
 
74
85
  @@defaults = {
75
- :smtp_ip => '127.0.0.1',
76
- :smtp_port => '1025',
77
- :http_ip => '127.0.0.1',
78
- :http_port => '1080',
86
+ :smtp_ip => "127.0.0.1",
87
+ :smtp_port => "1025",
88
+ :http_ip => "127.0.0.1",
89
+ :http_port => "1080",
90
+ :http_path => "/",
91
+ :messages_limit => nil,
79
92
  :verbose => false,
80
93
  :daemon => !windows?,
81
94
  :browse => false,
@@ -95,6 +108,9 @@ module MailCatcher extend self
95
108
  OptionParser.new do |parser|
96
109
  parser.banner = "Usage: mailcatcher [options]"
97
110
  parser.version = VERSION
111
+ parser.separator ""
112
+ parser.separator "MailCatcher v#{VERSION}"
113
+ parser.separator ""
98
114
 
99
115
  parser.on("--ip IP", "Set the ip address of both servers") do |ip|
100
116
  options[:smtp_ip] = options[:http_ip] = ip
@@ -116,6 +132,16 @@ module MailCatcher extend self
116
132
  options[:http_port] = port
117
133
  end
118
134
 
135
+ parser.on("--messages-limit COUNT", Integer, "Only keep up to COUNT most recent messages") do |count|
136
+ options[:messages_limit] = count
137
+ end
138
+
139
+ parser.on("--http-path PATH", String, "Add a prefix to all HTTP paths") do |path|
140
+ clean_path = Rack::Utils.clean_path_info("/#{path}")
141
+
142
+ options[:http_path] = clean_path
143
+ end
144
+
119
145
  parser.on("--no-quit", "Don't allow quitting the process") do
120
146
  options[:quit] = false
121
147
  end
@@ -128,32 +154,37 @@ module MailCatcher extend self
128
154
  end
129
155
 
130
156
  unless windows?
131
- parser.on('-f', '--foreground', 'Run in the foreground') do
157
+ parser.on("-f", "--foreground", "Run in the foreground") do
132
158
  options[:daemon] = false
133
159
  end
134
160
  end
135
161
 
136
162
  if browseable?
137
- parser.on('-b', '--browse', 'Open web browser') do
163
+ parser.on("-b", "--browse", "Open web browser") do
138
164
  options[:browse] = true
139
165
  end
140
166
  end
141
167
 
142
- parser.on('-v', '--verbose', 'Be more verbose') do
168
+ parser.on("-v", "--verbose", "Be more verbose") do
143
169
  options[:verbose] = true
144
170
  end
145
171
 
146
- parser.on('-h', '--help', 'Display this help information') do
172
+ parser.on_tail("-h", "--help", "Display this help information") do
147
173
  puts parser
148
174
  exit
149
175
  end
176
+
177
+ parser.on_tail("--version", "Display the current version") do
178
+ puts "mailcatcher #{VERSION}"
179
+ exit
180
+ end
150
181
  end.parse!
151
182
  end
152
183
  end
153
184
 
154
185
  def run! options=nil
155
186
  # If we are passed options, fill in the blanks
156
- options &&= options.reverse_merge @@defaults
187
+ options &&= @@defaults.merge options
157
188
  # Otherwise, parse them from ARGV
158
189
  options ||= parse!
159
190
 
@@ -167,7 +198,8 @@ module MailCatcher extend self
167
198
 
168
199
  puts "Starting MailCatcher"
169
200
 
170
- Thin::Logging.silent = (ENV["MAILCATCHER_ENV"] != "development")
201
+ Thin::Logging.debug = development?
202
+ Thin::Logging.silent = !development?
171
203
 
172
204
  # One EventMachine loop...
173
205
  EventMachine.run do
@@ -216,7 +248,7 @@ protected
216
248
  end
217
249
 
218
250
  def http_url
219
- "http://#{@@options[:http_ip]}:#{@@options[:http_port]}"
251
+ "http://#{@@options[:http_ip]}:#{@@options[:http_port]}#{@@options[:http_path]}"
220
252
  end
221
253
 
222
254
  def rescue_port port
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "eventmachine"
4
+
5
+ module MailCatcher
6
+ Bus = EventMachine::Channel.new
7
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "eventmachine"
2
4
  require "json"
3
5
  require "mail"
@@ -30,9 +32,11 @@ module MailCatcher::Mail extend self
30
32
  charset TEXT,
31
33
  body BLOB,
32
34
  size INTEGER,
33
- created_at DATETIME DEFAULT CURRENT_DATETIME
35
+ created_at DATETIME DEFAULT CURRENT_DATETIME,
36
+ FOREIGN KEY (message_id) REFERENCES message (id) ON DELETE CASCADE
34
37
  )
35
38
  SQL
39
+ db.foreign_keys = true
36
40
  end
37
41
  end
38
42
  end
@@ -54,7 +58,7 @@ module MailCatcher::Mail extend self
54
58
 
55
59
  EventMachine.next_tick do
56
60
  message = MailCatcher::Mail.message message_id
57
- MailCatcher::Events::MessageAdded.push message
61
+ MailCatcher::Bus.push(type: "add", message: message)
58
62
  end
59
63
  end
60
64
 
@@ -78,13 +82,19 @@ module MailCatcher::Mail extend self
78
82
  end
79
83
 
80
84
  def message(id)
81
- @message_query ||= db.prepare "SELECT * FROM message WHERE id = ? LIMIT 1"
85
+ @message_query ||= db.prepare "SELECT id, sender, recipients, subject, size, type, created_at FROM message WHERE id = ? LIMIT 1"
82
86
  row = @message_query.execute(id).next
83
87
  row && Hash[row.fields.zip(row)].tap do |message|
84
88
  message["recipients"] &&= JSON.parse(message["recipients"])
85
89
  end
86
90
  end
87
91
 
92
+ def message_source(id)
93
+ @message_source_query ||= db.prepare "SELECT source FROM message WHERE id = ? LIMIT 1"
94
+ row = @message_source_query.execute(id).next
95
+ row && row.first
96
+ end
97
+
88
98
  def message_has_html?(id)
89
99
  @message_has_html_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type IN ('application/xhtml+xml', 'text/html') LIMIT 1"
90
100
  (!!@message_has_html_query.execute(id).next) || ["text/html", "application/xhtml+xml"].include?(message(id)["type"])
@@ -145,16 +155,29 @@ module MailCatcher::Mail extend self
145
155
 
146
156
  def delete!
147
157
  @delete_all_messages_query ||= db.prepare "DELETE FROM message"
148
- @delete_all_message_parts_query ||= db.prepare "DELETE FROM message_part"
158
+ @delete_all_messages_query.execute
149
159
 
150
- @delete_all_messages_query.execute and
151
- @delete_all_message_parts_query.execute
160
+ EventMachine.next_tick do
161
+ MailCatcher::Bus.push(type: "clear")
162
+ end
152
163
  end
153
164
 
154
165
  def delete_message!(message_id)
155
166
  @delete_messages_query ||= db.prepare "DELETE FROM message WHERE id = ?"
156
- @delete_message_parts_query ||= db.prepare "DELETE FROM message_part WHERE message_id = ?"
157
- @delete_messages_query.execute(message_id) and
158
- @delete_message_parts_query.execute(message_id)
167
+ @delete_messages_query.execute(message_id)
168
+
169
+ EventMachine.next_tick do
170
+ MailCatcher::Bus.push(type: "remove", id: message_id)
171
+ end
172
+ end
173
+
174
+ def delete_older_messages!(count = MailCatcher.options[:messages_limit])
175
+ return if count.nil?
176
+ @older_messages_query ||= db.prepare "SELECT id FROM message WHERE id NOT IN (SELECT id FROM message ORDER BY created_at DESC LIMIT ?)"
177
+ @older_messages_query.execute(count).map do |row|
178
+ Hash[row.fields.zip(row)]
179
+ end.each do |message|
180
+ delete_message!(message["id"])
181
+ end
159
182
  end
160
183
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "eventmachine"
2
4
 
3
5
  require "mail_catcher/mail"
@@ -35,7 +37,7 @@ class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
35
37
  end
36
38
 
37
39
  def receive_data_chunk(lines)
38
- current_message[:source] ||= ""
40
+ current_message[:source] ||= +""
39
41
  lines.each do |line|
40
42
  current_message[:source] << line << "\r\n"
41
43
  end
@@ -44,6 +46,7 @@ class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
44
46
 
45
47
  def receive_message
46
48
  MailCatcher::Mail.add_message current_message
49
+ MailCatcher::Mail.delete_older_messages!
47
50
  puts "==> SMTP: Received message from '#{current_message[:sender]}' (#{current_message[:source].length} bytes)"
48
51
  true
49
52
  rescue => exception
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MailCatcher
2
- VERSION = "0.6.5"
4
+ VERSION = "0.8.0.beta3"
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rack/builder"
2
4
 
3
5
  require "mail_catcher/web/application"
@@ -6,12 +8,17 @@ module MailCatcher
6
8
  module Web extend self
7
9
  def app
8
10
  @@app ||= Rack::Builder.new do
9
- if ENV["MAILCATCHER_ENV"] == "development"
10
- require "mail_catcher/web/assets"
11
- map("/assets") { run Assets }
11
+ map(MailCatcher.options[:http_path]) do
12
+ if MailCatcher.development?
13
+ require "mail_catcher/web/assets"
14
+ map("/assets") { run Assets }
15
+ end
16
+
17
+ run Application
12
18
  end
13
19
 
14
- map("/") { run Application }
20
+ # This should only affect when http_path is anything but "/" above
21
+ run lambda { |env| [302, {"Location" => MailCatcher.options[:http_path]}, []] }
15
22
  end
16
23
  end
17
24