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 +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
|
|