mailcatcher 0.4.3 → 0.4.4
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.
- data/README.md +28 -6
- data/lib/mail_catcher.rb +58 -15
- data/lib/mail_catcher/growl.rb +16 -0
- data/lib/mail_catcher/mail.rb +19 -19
- data/lib/mail_catcher/smtp.rb +5 -5
- data/lib/mail_catcher/web.rb +16 -17
- data/public/stylesheets/application.css +41 -41
- metadata +10 -84
data/README.md
CHANGED
@@ -14,7 +14,8 @@ MailCatcher runs a super simple SMTP server which catches any message sent to it
|
|
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
|
-
* Mail appears instantly if your browser supports [WebSockets][websockets].
|
17
|
+
* Mail appears instantly if your browser supports [WebSockets][websockets], otherwise updates every thirty seconds.
|
18
|
+
* Growl notifications when you receive a new message.
|
18
19
|
* Runs as a daemon run in the background.
|
19
20
|
* Sendmail-analogue command, `catchmail`, makes [using mailcatcher from PHP][withphp] a lot easier.
|
20
21
|
* Written super-simply in EventMachine, easy to dig in and change.
|
@@ -28,14 +29,33 @@ MailCatcher runs a super simple SMTP server which catches any message sent to it
|
|
28
29
|
|
29
30
|
The brave can get the source from [the GitHub repository][mailcatcher-github].
|
30
31
|
|
31
|
-
|
32
|
+
### RVM
|
32
33
|
|
33
34
|
Under RVM your mailcatcher command may only 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:
|
34
35
|
|
35
36
|
rvm default@mailcatcher --create gem install mailcatcher
|
36
37
|
rvm wrapper default@mailcatcher --no-prefix mailcatcher catchmail
|
37
38
|
|
38
|
-
|
39
|
+
### Rails
|
40
|
+
|
41
|
+
To set up your rails app, I recommend adding this to your `environment/development.rb`:
|
42
|
+
|
43
|
+
config.action_mailer.delivery_method = :smtp
|
44
|
+
config.action_mailer.smtp_settings = { :host => "localhost", :port => 1025 }
|
45
|
+
|
46
|
+
### PHP
|
47
|
+
|
48
|
+
For projects using PHP, or PHP frameworks and application platforms like Drupal, you can set [PHP's mail configuration](http://www.php.net/manual/en/mail.configuration.php) in your [php.ini](http://www.php.net/manual/en/configuration.file.php) to send via MailCatcher with:
|
49
|
+
|
50
|
+
sendmail_path = /usr/bin/env catchmail
|
51
|
+
|
52
|
+
You can do this in an [Apache htaccess file](http://php.net/manual/en/configuration.changes.php) or general configuration like so:
|
53
|
+
|
54
|
+
php_value sendmail_path "/usr/bin/env catchmail"
|
55
|
+
|
56
|
+
If you've installed via RVM this probably won't work unless you've manually added your RVM bin paths to your system environment's PATH. In that case, run `which catchmail` and put that path into the `sendmail_path` directive above instead of `/usr/bin/env catchmail`.
|
57
|
+
|
58
|
+
### API
|
39
59
|
|
40
60
|
A fairly RESTful URL schema means you can download a list of messages in JSON from `/messages`, each message's metadata with `/messages/:id.json`, and then the pertinent parts with `/messages/:id.html` and `/messages/:id.plain` for the default HTML and plain text version, `/messages/:id/:cid` for individual attachments by CID, or the whole message with `/messages/:id.source`.
|
41
61
|
|
@@ -46,9 +66,11 @@ A fairly RESTful URL schema means you can download a list of messages in JSON fr
|
|
46
66
|
|
47
67
|
## TODO
|
48
68
|
|
49
|
-
* Growl support.
|
50
|
-
* Test suite.
|
51
69
|
* Add mail delivery on request, optionally multiple times.
|
70
|
+
* Better Growl support in MacRuby and RubyCocoa with click notifications which takes you to the received message.
|
71
|
+
* An API-compatible nodejs version, for fun and profit (and non-ruby npm users).
|
72
|
+
* Test suite.
|
73
|
+
* Compatibility testing against CampaignMonitor's [design guidelines](http://www.campaignmonitor.com/design-guidelines/) and [CSS support matrix](http://www.campaignmonitor.com/design-guidelines/).
|
52
74
|
* Forward mail to rendering service, maybe CampaignMonitor?
|
53
75
|
* Package as an app? Native interfaces? HotCocoa?
|
54
76
|
|
@@ -75,4 +97,4 @@ For dream catching, try [this](http://goo.gl/kgbh). OR [THIS](http://www.nyanico
|
|
75
97
|
[mailcatcher-github]: https://github.com/sj26/mailcatcher
|
76
98
|
[tfg]: http://www.thefrontiergroup.com.au
|
77
99
|
[websockets]: http://www.whatwg.org/specs/web-socket-protocol/
|
78
|
-
[withphp]: http://webschuur.com/publications/blogs/2011-05-29-catchmail_for_drupal_and_other_phpapplications_the_simple_version
|
100
|
+
[withphp]: http://webschuur.com/publications/blogs/2011-05-29-catchmail_for_drupal_and_other_phpapplications_the_simple_version
|
data/lib/mail_catcher.rb
CHANGED
@@ -4,52 +4,91 @@ require 'optparse'
|
|
4
4
|
require 'rbconfig'
|
5
5
|
require 'thin'
|
6
6
|
|
7
|
-
def windows?
|
8
|
-
Config::CONFIG['host_os'] =~ /mswin|mingw/
|
9
|
-
end
|
10
|
-
|
11
7
|
module MailCatcher
|
12
8
|
extend ActiveSupport::Autoload
|
13
9
|
|
14
10
|
autoload :Events
|
11
|
+
autoload :Growl
|
15
12
|
autoload :Mail
|
16
13
|
autoload :Smtp
|
17
14
|
autoload :Web
|
18
15
|
|
16
|
+
module_function
|
17
|
+
|
18
|
+
def mac?
|
19
|
+
Config::CONFIG['host_os'] =~ /darwin/
|
20
|
+
end
|
21
|
+
|
22
|
+
def windows?
|
23
|
+
Config::CONFIG['host_os'] =~ /mswin|mingw/
|
24
|
+
end
|
25
|
+
|
26
|
+
def macruby?
|
27
|
+
mac? and const_defined? :MACRUBY_VERSION
|
28
|
+
end
|
29
|
+
|
30
|
+
def growlnotify?
|
31
|
+
system "which", "-s", "growlnotify"
|
32
|
+
end
|
33
|
+
|
34
|
+
def growlframework?
|
35
|
+
macruby? and
|
36
|
+
# TODO: Look for growl framework accessible
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def growl?
|
41
|
+
growlnotify? or growlframework?
|
42
|
+
end
|
43
|
+
|
19
44
|
@@defaults = {
|
20
45
|
:smtp_ip => '127.0.0.1',
|
21
46
|
:smtp_port => '1025',
|
22
47
|
:http_ip => '127.0.0.1',
|
23
48
|
:http_port => '1080',
|
24
49
|
:verbose => false,
|
25
|
-
:daemon =>
|
50
|
+
:daemon => !windows?,
|
51
|
+
:growl => growlnotify?,
|
26
52
|
}
|
27
53
|
|
28
|
-
def
|
54
|
+
def parse! arguments=ARGV, defaults=@@defaults
|
29
55
|
@@defaults.dup.tap do |options|
|
30
56
|
OptionParser.new do |parser|
|
31
|
-
parser.banner =
|
57
|
+
parser.banner = "Usage: mailcatcher [options]"
|
32
58
|
|
33
|
-
parser.on(
|
59
|
+
parser.on("--ip IP", "Set the ip address of both servers") do |ip|
|
34
60
|
options[:smtp_ip] = options[:http_ip] = ip
|
35
61
|
end
|
36
62
|
|
37
|
-
parser.on(
|
63
|
+
parser.on("--smtp-ip IP", "Set the ip address of the smtp server") do |ip|
|
38
64
|
options[:smtp_ip] = ip
|
39
65
|
end
|
40
66
|
|
41
|
-
parser.on(
|
67
|
+
parser.on("--smtp-port PORT", Integer, "Set the port of the smtp server") do |port|
|
42
68
|
options[:smtp_port] = port
|
43
69
|
end
|
44
70
|
|
45
|
-
parser.on(
|
71
|
+
parser.on("--http-ip IP", "Set the ip address of the http server") do |ip|
|
46
72
|
options[:http_ip] = ip
|
47
73
|
end
|
48
74
|
|
49
|
-
parser.on(
|
75
|
+
parser.on("--http-port PORT", Integer, "Set the port address of the http server") do |port|
|
50
76
|
options[:http_port] = port
|
51
77
|
end
|
52
78
|
|
79
|
+
if mac?
|
80
|
+
parser.on("--[no-]growl", "Growl to the local machine when a message arrives") do |growl|
|
81
|
+
if growl and not growlnotify?
|
82
|
+
puts "You'll need to install growlnotify from the Growl installer."
|
83
|
+
puts
|
84
|
+
puts "See: http://growl.info/extras.php#growlnotify"
|
85
|
+
exit!
|
86
|
+
end
|
87
|
+
|
88
|
+
options[:growl] = growl
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
53
92
|
unless windows?
|
54
93
|
parser.on('-f', '--foreground', 'Run in the foreground') do
|
55
94
|
options[:daemon] = false
|
@@ -68,7 +107,7 @@ module MailCatcher
|
|
68
107
|
end
|
69
108
|
end
|
70
109
|
|
71
|
-
def
|
110
|
+
def run! options=nil
|
72
111
|
# If we are passed options, fill in the blanks
|
73
112
|
options &&= @@defaults.merge options
|
74
113
|
# Otherwise, parse them from ARGV
|
@@ -80,6 +119,9 @@ module MailCatcher
|
|
80
119
|
|
81
120
|
# One EventMachine loop...
|
82
121
|
EventMachine.run do
|
122
|
+
# Get our lion on if asked
|
123
|
+
MailCatcher::Growl.start if options[:growl]
|
124
|
+
|
83
125
|
# TODO: DRY this up
|
84
126
|
|
85
127
|
# Set up an SMTP server to run within EventMachine
|
@@ -105,13 +147,14 @@ module MailCatcher
|
|
105
147
|
end
|
106
148
|
end
|
107
149
|
|
108
|
-
def
|
150
|
+
def quit!
|
109
151
|
EventMachine.next_tick { EventMachine.stop_event_loop }
|
110
152
|
end
|
111
153
|
|
112
154
|
protected
|
155
|
+
module_function
|
113
156
|
|
114
|
-
def
|
157
|
+
def rescue_port port
|
115
158
|
begin
|
116
159
|
yield
|
117
160
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module MailCatcher
|
2
|
+
module Growl
|
3
|
+
module_function
|
4
|
+
def start
|
5
|
+
MailCatcher::Events::MessageAdded.subscribe MailCatcher::Growl.method :notify
|
6
|
+
end
|
7
|
+
|
8
|
+
def notify message
|
9
|
+
system "growlnotify", "--name", "MailCatcher", "--message", "Message received:\n#{message["subject"]}"
|
10
|
+
end
|
11
|
+
|
12
|
+
# TODO: Native support on MacRuby with click backs
|
13
|
+
#def click
|
14
|
+
#end
|
15
|
+
end
|
16
|
+
end
|
data/lib/mail_catcher/mail.rb
CHANGED
@@ -36,10 +36,10 @@ module MailCatcher::Mail
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
def add_message(message)
|
41
41
|
@@add_message_query ||= db.prepare("INSERT INTO message (sender, recipients, subject, source, type, size, created_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'))")
|
42
|
-
|
42
|
+
|
43
43
|
mail = Mail.new(message[:source])
|
44
44
|
result = @@add_message_query.execute(message[:sender], message[:recipients].to_json, mail.subject, message[:source], mail.mime_type || 'text/plain', message[:source].length)
|
45
45
|
message_id = db.last_insert_row_id
|
@@ -51,7 +51,7 @@ module MailCatcher::Mail
|
|
51
51
|
cid = part.cid if part.respond_to? :cid
|
52
52
|
add_message_part(message_id, cid, part.mime_type || 'text/plain', part.attachment? ? 1 : 0, part.filename, part.charset, body, body.length)
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
EventMachine.next_tick do
|
56
56
|
message = MailCatcher::Mail.message message_id
|
57
57
|
MailCatcher::Events::MessageAdded.push message
|
@@ -62,12 +62,12 @@ module MailCatcher::Mail
|
|
62
62
|
@@add_message_part_query ||= db.prepare "INSERT INTO message_part (message_id, cid, type, is_attachment, filename, charset, body, size, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))"
|
63
63
|
@@add_message_part_query.execute(*args)
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
def latest_created_at
|
67
67
|
@@latest_created_at_query ||= db.prepare "SELECT created_at FROM message ORDER BY created_at DESC LIMIT 1"
|
68
68
|
@@latest_created_at_query.execute.next
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
def messages
|
72
72
|
@@messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at ASC"
|
73
73
|
@@messages_query.execute.map do |row|
|
@@ -76,7 +76,7 @@ module MailCatcher::Mail
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
def message(id)
|
81
81
|
@@message_query ||= db.prepare "SELECT * FROM message WHERE id = ? LIMIT 1"
|
82
82
|
row = @@message_query.execute(id).next
|
@@ -84,56 +84,56 @@ module MailCatcher::Mail
|
|
84
84
|
message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
|
85
85
|
end
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
def message_has_html?(id)
|
89
89
|
@@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
90
|
(!!@@message_has_html_query.execute(id).next) || ['text/html', 'application/xhtml+xml'].include?(message(id)["type"])
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
def message_has_plain?(id)
|
94
94
|
@@message_has_plain_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type = 'text/plain' LIMIT 1"
|
95
95
|
(!!@@message_has_plain_query.execute(id).next) || message(id)["type"] == "text/plain"
|
96
96
|
end
|
97
|
-
|
97
|
+
|
98
98
|
def message_parts(id)
|
99
99
|
@@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? ORDER BY filename ASC"
|
100
100
|
@@message_parts_query.execute(id).map do |row|
|
101
101
|
Hash[row.fields.zip(row)]
|
102
102
|
end
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
def message_attachments(id)
|
106
106
|
@@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? AND is_attachment = 1 ORDER BY filename ASC"
|
107
107
|
@@message_parts_query.execute(id).map do |row|
|
108
108
|
Hash[row.fields.zip(row)]
|
109
109
|
end
|
110
110
|
end
|
111
|
-
|
111
|
+
|
112
112
|
def message_part(message_id, part_id)
|
113
113
|
@@message_part_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND id = ? LIMIT 1"
|
114
114
|
row = @@message_part_query.execute(message_id, part_id).next
|
115
115
|
row && Hash[row.fields.zip(row)]
|
116
116
|
end
|
117
|
-
|
117
|
+
|
118
118
|
def message_part_type(message_id, part_type)
|
119
119
|
@@message_part_type_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND type = ? AND is_attachment = 0 LIMIT 1"
|
120
120
|
row = @@message_part_type_query.execute(message_id, part_type).next
|
121
121
|
row && Hash[row.fields.zip(row)]
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
def message_part_html(message_id)
|
125
125
|
part = message_part_type(message_id, "text/html")
|
126
126
|
part ||= message_part_type(message_id, "application/xhtml+xml")
|
127
127
|
part ||= begin
|
128
128
|
message = message(message_id)
|
129
|
-
message if ['text/html', 'application/xhtml+xml'].include? message["type"]
|
129
|
+
message if message.present? and ['text/html', 'application/xhtml+xml'].include? message["type"]
|
130
130
|
end
|
131
131
|
end
|
132
|
-
|
132
|
+
|
133
133
|
def message_part_plain(message_id)
|
134
134
|
message_part_type message_id, "text/plain"
|
135
135
|
end
|
136
|
-
|
136
|
+
|
137
137
|
def message_part_cid(message_id, cid)
|
138
138
|
@@message_part_cid_query ||= db.prepare 'SELECT * FROM message_part WHERE message_id = ?'
|
139
139
|
@@message_part_cid_query.execute(message_id).map do |row|
|
@@ -142,13 +142,13 @@ module MailCatcher::Mail
|
|
142
142
|
part["cid"] == cid
|
143
143
|
end
|
144
144
|
end
|
145
|
-
|
145
|
+
|
146
146
|
def delete!
|
147
147
|
@@delete_messages_query ||= db.prepare 'DELETE FROM message'
|
148
148
|
@@delete_message_parts_query ||= db.prepare 'DELETE FROM message_part'
|
149
|
-
|
149
|
+
|
150
150
|
@@delete_messages_query.execute and
|
151
151
|
@@delete_message_parts_query.execute
|
152
152
|
end
|
153
153
|
end
|
154
|
-
end
|
154
|
+
end
|
data/lib/mail_catcher/smtp.rb
CHANGED
@@ -5,23 +5,23 @@ module MailCatcher
|
|
5
5
|
def current_message
|
6
6
|
@current_message ||= {}
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def receive_reset
|
10
10
|
@current_message = nil
|
11
11
|
true
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def receive_sender(sender)
|
15
15
|
current_message[:sender] = sender
|
16
16
|
true
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def receive_recipient(recipient)
|
20
20
|
current_message[:recipients] ||= []
|
21
21
|
current_message[:recipients] << recipient
|
22
22
|
true
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def receive_data_chunk(lines)
|
26
26
|
current_message[:source] ||= ""
|
27
27
|
current_message[:source] += lines.join("\n")
|
@@ -45,4 +45,4 @@ module MailCatcher
|
|
45
45
|
@current_message = nil
|
46
46
|
end
|
47
47
|
end
|
48
|
-
end
|
48
|
+
end
|
data/lib/mail_catcher/web.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'sinatra'
|
2
|
-
require 'json'
|
3
2
|
require 'pathname'
|
4
3
|
|
5
4
|
require 'skinny'
|
@@ -12,16 +11,16 @@ module MailCatcher
|
|
12
11
|
class Web < Sinatra::Base
|
13
12
|
set :root, Pathname.new(__FILE__).dirname.parent.parent
|
14
13
|
set :haml, :format => :html5
|
15
|
-
|
14
|
+
|
16
15
|
get '/' do
|
17
16
|
haml :index
|
18
17
|
end
|
19
|
-
|
18
|
+
|
20
19
|
delete '/' do
|
21
20
|
MailCatcher.quit!
|
22
21
|
status 204
|
23
22
|
end
|
24
|
-
|
23
|
+
|
25
24
|
get '/messages' do
|
26
25
|
if request.websocket?
|
27
26
|
request.websocket!(
|
@@ -36,12 +35,12 @@ module MailCatcher
|
|
36
35
|
MailCatcher::Mail.messages.to_json
|
37
36
|
end
|
38
37
|
end
|
39
|
-
|
38
|
+
|
40
39
|
delete '/messages' do
|
41
40
|
MailCatcher::Mail.delete!
|
42
41
|
status 204
|
43
42
|
end
|
44
|
-
|
43
|
+
|
45
44
|
get '/messages/:id.json' do
|
46
45
|
id = params[:id].to_i
|
47
46
|
if message = MailCatcher::Mail.message(id)
|
@@ -59,26 +58,26 @@ module MailCatcher
|
|
59
58
|
not_found
|
60
59
|
end
|
61
60
|
end
|
62
|
-
|
61
|
+
|
63
62
|
get '/messages/:id.html' do
|
64
63
|
id = params[:id].to_i
|
65
64
|
if part = MailCatcher::Mail.message_part_html(id)
|
66
65
|
content_type part["type"], :charset => (part["charset"] || "utf8")
|
67
|
-
|
66
|
+
|
68
67
|
body = part["body"]
|
69
|
-
|
68
|
+
|
70
69
|
# Rewrite body to link to embedded attachments served by cid
|
71
70
|
body.gsub! /cid:([^'"> ]+)/, "#{id}/\\1"
|
72
|
-
|
71
|
+
|
73
72
|
# Rewrite body to open links in a new window
|
74
73
|
body.gsub! /<a\s+/, '<a target="_blank" '
|
75
|
-
|
74
|
+
|
76
75
|
body
|
77
76
|
else
|
78
77
|
not_found
|
79
78
|
end
|
80
79
|
end
|
81
|
-
|
80
|
+
|
82
81
|
get "/messages/:id.plain" do
|
83
82
|
id = params[:id].to_i
|
84
83
|
if part = MailCatcher::Mail.message_part_plain(id)
|
@@ -88,7 +87,7 @@ module MailCatcher
|
|
88
87
|
not_found
|
89
88
|
end
|
90
89
|
end
|
91
|
-
|
90
|
+
|
92
91
|
get "/messages/:id.source" do
|
93
92
|
id = params[:id].to_i
|
94
93
|
if message = MailCatcher::Mail.message(id)
|
@@ -98,7 +97,7 @@ module MailCatcher
|
|
98
97
|
not_found
|
99
98
|
end
|
100
99
|
end
|
101
|
-
|
100
|
+
|
102
101
|
get "/messages/:id.eml" do
|
103
102
|
id = params[:id].to_i
|
104
103
|
if message = MailCatcher::Mail.message(id)
|
@@ -108,7 +107,7 @@ module MailCatcher
|
|
108
107
|
not_found
|
109
108
|
end
|
110
109
|
end
|
111
|
-
|
110
|
+
|
112
111
|
get "/messages/:id/:cid" do
|
113
112
|
id = params[:id].to_i
|
114
113
|
if part = MailCatcher::Mail.message_part_cid(id, params[:cid])
|
@@ -119,9 +118,9 @@ module MailCatcher
|
|
119
118
|
not_found
|
120
119
|
end
|
121
120
|
end
|
122
|
-
|
121
|
+
|
123
122
|
not_found do
|
124
123
|
"<html><body><h1>No Dice</h1><p>The message you were looking for does not exist, or doesn't have content of this type.</p></body></html>"
|
125
124
|
end
|
126
125
|
end
|
127
|
-
end
|
126
|
+
end
|
@@ -57,8 +57,8 @@ body {
|
|
57
57
|
-moz-box-orient: vertical;
|
58
58
|
-webkit-box-orient: vertical;
|
59
59
|
box-orient: vertical;
|
60
|
-
background: #
|
61
|
-
color:
|
60
|
+
background: #eeeeee;
|
61
|
+
color: black;
|
62
62
|
font-size: 12px;
|
63
63
|
font-family: Helvetica, sans-serif; }
|
64
64
|
body body {
|
@@ -70,7 +70,7 @@ body {
|
|
70
70
|
body > header {
|
71
71
|
overflow: hidden;
|
72
72
|
*zoom: 1;
|
73
|
-
border-bottom: 1px solid #
|
73
|
+
border-bottom: 1px solid #cccccc; }
|
74
74
|
body > header h1 {
|
75
75
|
float: left;
|
76
76
|
padding: 6px;
|
@@ -89,10 +89,10 @@ body > header {
|
|
89
89
|
-o-transition-duration: 1s;
|
90
90
|
transition-duration: 1s; }
|
91
91
|
body > header h1 a:hover {
|
92
|
-
color: #
|
92
|
+
color: #4183c4; }
|
93
93
|
body > header nav {
|
94
|
-
border-left: 1px solid #
|
95
|
-
border-right: 1px solid
|
94
|
+
border-left: 1px solid #cccccc;
|
95
|
+
border-right: 1px solid white; }
|
96
96
|
body > header nav.project {
|
97
97
|
float: left; }
|
98
98
|
body > header nav.app {
|
@@ -103,15 +103,15 @@ body > header {
|
|
103
103
|
display: inline-block;
|
104
104
|
float: left;
|
105
105
|
padding: 9px;
|
106
|
-
border-left: 1px solid
|
107
|
-
border-right: 1px solid #
|
108
|
-
background: #
|
109
|
-
color: #
|
106
|
+
border-left: 1px solid white;
|
107
|
+
border-right: 1px solid #cccccc;
|
108
|
+
background: #eeeeee;
|
109
|
+
color: #666666;
|
110
110
|
font-weight: bold;
|
111
111
|
text-decoration: none;
|
112
112
|
text-shadow: 0 1px 0 white; }
|
113
113
|
body > header nav li a:hover {
|
114
|
-
background:
|
114
|
+
background: white; }
|
115
115
|
body > header nav li a:active {
|
116
116
|
margin: 1px -1px -1px 1px;
|
117
117
|
-moz-box-shadow: none;
|
@@ -123,18 +123,18 @@ body > header {
|
|
123
123
|
width: 100%;
|
124
124
|
height: 10em;
|
125
125
|
overflow: auto;
|
126
|
-
background:
|
127
|
-
border-top: 1px solid
|
128
|
-
border-bottom: 1px solid #
|
126
|
+
background: white;
|
127
|
+
border-top: 1px solid white;
|
128
|
+
border-bottom: 1px solid #cccccc; }
|
129
129
|
#messages table {
|
130
130
|
width: 100%; }
|
131
131
|
#messages table thead tr {
|
132
|
-
background: #
|
133
|
-
color: #
|
132
|
+
background: #eeeeee;
|
133
|
+
color: #333333; }
|
134
134
|
#messages table thead tr th {
|
135
|
-
padding: .25em;
|
135
|
+
padding: 0.25em;
|
136
136
|
font-weight: bold;
|
137
|
-
color: #
|
137
|
+
color: #666666;
|
138
138
|
text-shadow: 0 1px 0 white; }
|
139
139
|
#messages table tbody tr:nth-child(even) {
|
140
140
|
background: #f0f0f0; }
|
@@ -142,7 +142,7 @@ body > header {
|
|
142
142
|
background: Highlight;
|
143
143
|
color: HighlightText; }
|
144
144
|
#messages table tbody tr td {
|
145
|
-
padding: .25em; }
|
145
|
+
padding: 0.25em; }
|
146
146
|
|
147
147
|
#message {
|
148
148
|
display: -moz-box;
|
@@ -154,24 +154,24 @@ body > header {
|
|
154
154
|
-moz-box-flex: 1;
|
155
155
|
-webkit-box-flex: 1;
|
156
156
|
box-flex: 1;
|
157
|
-
border-top: 1px solid
|
157
|
+
border-top: 1px solid white; }
|
158
158
|
#message > header {
|
159
159
|
overflow: hidden;
|
160
160
|
*zoom: 1; }
|
161
161
|
#message > header .metadata {
|
162
162
|
overflow: hidden;
|
163
163
|
*zoom: 1;
|
164
|
-
padding: .5em; }
|
164
|
+
padding: 0.5em; }
|
165
165
|
#message > header .metadata dt, #message > header .metadata dd {
|
166
|
-
padding: .25em; }
|
166
|
+
padding: 0.25em; }
|
167
167
|
#message > header .metadata dt {
|
168
168
|
float: left;
|
169
169
|
clear: left;
|
170
170
|
width: 8em;
|
171
|
-
margin-right: .5em;
|
171
|
+
margin-right: 0.5em;
|
172
172
|
text-align: right;
|
173
173
|
font-weight: bold;
|
174
|
-
color: #
|
174
|
+
color: #666666;
|
175
175
|
text-shadow: 0 1px 0 white; }
|
176
176
|
#message > header .metadata dd.subject {
|
177
177
|
font-weight: bold; }
|
@@ -185,26 +185,26 @@ body > header {
|
|
185
185
|
display: inline-block;
|
186
186
|
vertical-align: middle;
|
187
187
|
*vertical-align: auto;
|
188
|
-
margin-right: .5em; }
|
188
|
+
margin-right: 0.5em; }
|
189
189
|
#message > header .metadata .attachments ul li {
|
190
190
|
*display: inline; }
|
191
191
|
#message > header .views ul {
|
192
|
-
padding: 0 .5em;
|
193
|
-
border-bottom: 1px solid #
|
192
|
+
padding: 0 0.5em;
|
193
|
+
border-bottom: 1px solid #999999; }
|
194
194
|
#message > header .views .tab {
|
195
195
|
display: inline-block;
|
196
|
-
padding: .5em;
|
197
|
-
border: 1px solid #
|
198
|
-
background: #
|
199
|
-
color: #
|
196
|
+
padding: 0.5em;
|
197
|
+
border: 1px solid #999999;
|
198
|
+
background: #dddddd;
|
199
|
+
color: #333333;
|
200
200
|
border-width: 1px 1px 0 1px;
|
201
201
|
cursor: pointer;
|
202
202
|
text-shadow: 0 1px 0 #eeeeee; }
|
203
203
|
#message > header .views .tab:not(.selected):hover {
|
204
|
-
background-color: #
|
204
|
+
background-color: #dddddd; }
|
205
205
|
#message > header .views .tab.selected {
|
206
|
-
background:
|
207
|
-
color:
|
206
|
+
background: white;
|
207
|
+
color: black;
|
208
208
|
height: 13px;
|
209
209
|
-moz-box-shadow: 1px 1px 0 #cccccc;
|
210
210
|
-webkit-box-shadow: 1px 1px 0 #cccccc;
|
@@ -214,13 +214,13 @@ body > header {
|
|
214
214
|
#message > header .views .button {
|
215
215
|
display: inline-block;
|
216
216
|
float: right;
|
217
|
-
margin: 0 .25em; }
|
217
|
+
margin: 0 0.25em; }
|
218
218
|
#message > header .views .button a {
|
219
219
|
display: inline-block;
|
220
|
-
padding: .25em .5em;
|
221
|
-
border: 1px solid #
|
222
|
-
background: #
|
223
|
-
color: #
|
220
|
+
padding: 0.25em 0.5em;
|
221
|
+
border: 1px solid #999999;
|
222
|
+
background: #dddddd;
|
223
|
+
color: #333333;
|
224
224
|
text-decoration: none;
|
225
225
|
text-shadow: 1px 1px 0 #eeeeee;
|
226
226
|
-moz-box-shadow: 1px 1px 0 #cccccc;
|
@@ -228,7 +228,7 @@ body > header {
|
|
228
228
|
-o-box-shadow: 1px 1px 0 #cccccc;
|
229
229
|
box-shadow: 1px 1px 0 #cccccc; }
|
230
230
|
#message > header .views .button a:hover {
|
231
|
-
background:
|
231
|
+
background: white;
|
232
232
|
text-shadow: none; }
|
233
233
|
#message > header .views .button a:active {
|
234
234
|
margin: 1px -1px -1px 1px;
|
@@ -244,4 +244,4 @@ body > header {
|
|
244
244
|
-webkit-box-flex: 1;
|
245
245
|
box-flex: 1;
|
246
246
|
width: 100%;
|
247
|
-
background:
|
247
|
+
background: white; }
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mailcatcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 9
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 4
|
9
|
-
- 3
|
10
|
-
version: 0.4.3
|
5
|
+
version: 0.4.4
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Samuel Cochran
|
@@ -15,8 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date: 2011-06-
|
19
|
-
default_executable:
|
13
|
+
date: 2011-06-10 00:00:00 Z
|
20
14
|
dependencies:
|
21
15
|
- !ruby/object:Gem::Dependency
|
22
16
|
name: activesupport
|
@@ -26,10 +20,6 @@ dependencies:
|
|
26
20
|
requirements:
|
27
21
|
- - ~>
|
28
22
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 7
|
30
|
-
segments:
|
31
|
-
- 3
|
32
|
-
- 0
|
33
23
|
version: "3.0"
|
34
24
|
type: :runtime
|
35
25
|
version_requirements: *id001
|
@@ -41,10 +31,6 @@ dependencies:
|
|
41
31
|
requirements:
|
42
32
|
- - ~>
|
43
33
|
- !ruby/object:Gem::Version
|
44
|
-
hash: 19
|
45
|
-
segments:
|
46
|
-
- 0
|
47
|
-
- 12
|
48
34
|
version: "0.12"
|
49
35
|
type: :runtime
|
50
36
|
version_requirements: *id002
|
@@ -56,10 +42,6 @@ dependencies:
|
|
56
42
|
requirements:
|
57
43
|
- - ~>
|
58
44
|
- !ruby/object:Gem::Version
|
59
|
-
hash: 5
|
60
|
-
segments:
|
61
|
-
- 2
|
62
|
-
- 3
|
63
45
|
version: "2.3"
|
64
46
|
type: :runtime
|
65
47
|
version_requirements: *id003
|
@@ -71,10 +53,6 @@ dependencies:
|
|
71
53
|
requirements:
|
72
54
|
- - ~>
|
73
55
|
- !ruby/object:Gem::Version
|
74
|
-
hash: 9
|
75
|
-
segments:
|
76
|
-
- 1
|
77
|
-
- 3
|
78
56
|
version: "1.3"
|
79
57
|
type: :runtime
|
80
58
|
version_requirements: *id004
|
@@ -86,10 +64,6 @@ dependencies:
|
|
86
64
|
requirements:
|
87
65
|
- - ~>
|
88
66
|
- !ruby/object:Gem::Version
|
89
|
-
hash: 11
|
90
|
-
segments:
|
91
|
-
- 1
|
92
|
-
- 2
|
93
67
|
version: "1.2"
|
94
68
|
type: :runtime
|
95
69
|
version_requirements: *id005
|
@@ -101,10 +75,6 @@ dependencies:
|
|
101
75
|
requirements:
|
102
76
|
- - ~>
|
103
77
|
- !ruby/object:Gem::Version
|
104
|
-
hash: 9
|
105
|
-
segments:
|
106
|
-
- 0
|
107
|
-
- 1
|
108
78
|
version: "0.1"
|
109
79
|
type: :runtime
|
110
80
|
version_requirements: *id006
|
@@ -116,10 +86,6 @@ dependencies:
|
|
116
86
|
requirements:
|
117
87
|
- - ~>
|
118
88
|
- !ruby/object:Gem::Version
|
119
|
-
hash: 11
|
120
|
-
segments:
|
121
|
-
- 1
|
122
|
-
- 2
|
123
89
|
version: "1.2"
|
124
90
|
type: :runtime
|
125
91
|
version_requirements: *id007
|
@@ -131,74 +97,42 @@ dependencies:
|
|
131
97
|
requirements:
|
132
98
|
- - ~>
|
133
99
|
- !ruby/object:Gem::Version
|
134
|
-
hash: 5
|
135
|
-
segments:
|
136
|
-
- 3
|
137
|
-
- 1
|
138
100
|
version: "3.1"
|
139
101
|
type: :runtime
|
140
102
|
version_requirements: *id008
|
141
|
-
- !ruby/object:Gem::Dependency
|
142
|
-
name: json
|
143
|
-
prerelease: false
|
144
|
-
requirement: &id009 !ruby/object:Gem::Requirement
|
145
|
-
none: false
|
146
|
-
requirements:
|
147
|
-
- - ~>
|
148
|
-
- !ruby/object:Gem::Version
|
149
|
-
hash: 15
|
150
|
-
segments:
|
151
|
-
- 1
|
152
|
-
- 0
|
153
|
-
version: "1.0"
|
154
|
-
type: :runtime
|
155
|
-
version_requirements: *id009
|
156
103
|
- !ruby/object:Gem::Dependency
|
157
104
|
name: sass
|
158
105
|
prerelease: false
|
159
|
-
requirement: &
|
106
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
160
107
|
none: false
|
161
108
|
requirements:
|
162
109
|
- - ~>
|
163
110
|
- !ruby/object:Gem::Version
|
164
|
-
hash: 5
|
165
|
-
segments:
|
166
|
-
- 3
|
167
|
-
- 1
|
168
111
|
version: "3.1"
|
169
112
|
type: :development
|
170
|
-
version_requirements: *
|
113
|
+
version_requirements: *id009
|
171
114
|
- !ruby/object:Gem::Dependency
|
172
115
|
name: compass
|
173
116
|
prerelease: false
|
174
|
-
requirement: &
|
117
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
175
118
|
none: false
|
176
119
|
requirements:
|
177
120
|
- - ~>
|
178
121
|
- !ruby/object:Gem::Version
|
179
|
-
hash: 49
|
180
|
-
segments:
|
181
|
-
- 0
|
182
|
-
- 11
|
183
|
-
- 1
|
184
122
|
version: 0.11.1
|
185
123
|
type: :development
|
186
|
-
version_requirements: *
|
124
|
+
version_requirements: *id010
|
187
125
|
- !ruby/object:Gem::Dependency
|
188
126
|
name: coffee-script
|
189
127
|
prerelease: false
|
190
|
-
requirement: &
|
128
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
191
129
|
none: false
|
192
130
|
requirements:
|
193
131
|
- - ~>
|
194
132
|
- !ruby/object:Gem::Version
|
195
|
-
hash: 7
|
196
|
-
segments:
|
197
|
-
- 2
|
198
|
-
- 2
|
199
133
|
version: "2.2"
|
200
134
|
type: :development
|
201
|
-
version_requirements: *
|
135
|
+
version_requirements: *id011
|
202
136
|
description: " MailCatcher runs a super simple SMTP server which catches any\n message sent to it to display in a web interface. Run\n mailcatcher, set your favourite app to deliver to\n smtp://127.0.0.1:1025 instead of your default SMTP server,\n then check out http://127.0.0.1:1080 to see the mail.\n"
|
203
137
|
email: sj26@sj26.com
|
204
138
|
executables:
|
@@ -215,6 +149,7 @@ files:
|
|
215
149
|
- bin/catchmail
|
216
150
|
- bin/mailcatcher
|
217
151
|
- lib/mail_catcher/events.rb
|
152
|
+
- lib/mail_catcher/growl.rb
|
218
153
|
- lib/mail_catcher/mail.rb
|
219
154
|
- lib/mail_catcher/smtp.rb
|
220
155
|
- lib/mail_catcher/web.rb
|
@@ -224,7 +159,6 @@ files:
|
|
224
159
|
- public/javascripts/jquery.js
|
225
160
|
- public/stylesheets/application.css
|
226
161
|
- views/index.haml
|
227
|
-
has_rdoc: true
|
228
162
|
homepage: http://github.com/sj26/mailcatcher
|
229
163
|
licenses: []
|
230
164
|
|
@@ -238,25 +172,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
238
172
|
requirements:
|
239
173
|
- - ">="
|
240
174
|
- !ruby/object:Gem::Version
|
241
|
-
hash: 57
|
242
|
-
segments:
|
243
|
-
- 1
|
244
|
-
- 8
|
245
|
-
- 7
|
246
175
|
version: 1.8.7
|
247
176
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
248
177
|
none: false
|
249
178
|
requirements:
|
250
179
|
- - ">="
|
251
180
|
- !ruby/object:Gem::Version
|
252
|
-
hash: 3
|
253
|
-
segments:
|
254
|
-
- 0
|
255
181
|
version: "0"
|
256
182
|
requirements: []
|
257
183
|
|
258
184
|
rubyforge_project:
|
259
|
-
rubygems_version: 1.
|
185
|
+
rubygems_version: 1.8.4
|
260
186
|
signing_key:
|
261
187
|
specification_version: 3
|
262
188
|
summary: Runs an SMTP server, catches and displays email in a web interface.
|