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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/README.md +30 -27
- data/bin/catchmail +1 -0
- data/bin/mailcatcher +1 -0
- data/lib/mail_catcher.rb +50 -18
- data/lib/mail_catcher/bus.rb +7 -0
- data/lib/mail_catcher/mail.rb +32 -9
- data/lib/mail_catcher/smtp.rb +4 -1
- data/lib/mail_catcher/version.rb +3 -1
- data/lib/mail_catcher/web.rb +11 -4
- data/lib/mail_catcher/web/application.rb +16 -18
- data/lib/mailcatcher.rb +2 -0
- data/public/assets/mailcatcher.css +1 -1
- data/public/assets/mailcatcher.js +5 -5
- data/views/index.erb +4 -3
- metadata +23 -24
- metadata.gz.sig +0 -0
- data/lib/mail_catcher/events.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1fbbb01ee21a1d0946b2a6ba1e52b999567a8894b8f6b8816f4d796d924b4967
|
4
|
+
data.tar.gz: 6d6cc1c27dd58ce096fe1458075ce2ae04a30a22abba3629cc1ddbc9c7f3a7f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
19
|
-
* Sendmail-analogue command, `catchmail`, makes
|
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://
|
28
|
-
4. Send mail through smtp://
|
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
|
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
|
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
|
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 =>
|
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,
|
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
|
-
*
|
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-
|
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
data/bin/mailcatcher
CHANGED
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[
|
51
|
+
RbConfig::CONFIG["host_os"] =~ /darwin/
|
41
52
|
end
|
42
53
|
|
43
54
|
def windows?
|
44
|
-
RbConfig::CONFIG[
|
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(
|
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 =>
|
76
|
-
:smtp_port =>
|
77
|
-
:http_ip =>
|
78
|
-
:http_port =>
|
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(
|
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(
|
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(
|
168
|
+
parser.on("-v", "--verbose", "Be more verbose") do
|
143
169
|
options[:verbose] = true
|
144
170
|
end
|
145
171
|
|
146
|
-
parser.
|
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 &&=
|
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.
|
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
|
data/lib/mail_catcher/mail.rb
CHANGED
@@ -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::
|
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
|
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
|
-
@
|
158
|
+
@delete_all_messages_query.execute
|
149
159
|
|
150
|
-
|
151
|
-
|
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
|
-
@
|
157
|
-
|
158
|
-
|
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
|
data/lib/mail_catcher/smtp.rb
CHANGED
@@ -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
|
data/lib/mail_catcher/version.rb
CHANGED
data/lib/mail_catcher/web.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
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
|
|