mailcatcher 0.6.5 → 0.8.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
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