mailcatcher 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
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
- ## RVM
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
- ## API
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
@@ -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 => (true unless windows?),
50
+ :daemon => !windows?,
51
+ :growl => growlnotify?,
26
52
  }
27
53
 
28
- def self.parse! arguments=ARGV, defaults=@@defaults
54
+ def parse! arguments=ARGV, defaults=@@defaults
29
55
  @@defaults.dup.tap do |options|
30
56
  OptionParser.new do |parser|
31
- parser.banner = 'Usage: mailcatcher [options]'
57
+ parser.banner = "Usage: mailcatcher [options]"
32
58
 
33
- parser.on('--ip IP', 'Set the ip address of both servers') do |ip|
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('--smtp-ip IP', 'Set the ip address of the smtp server') do |ip|
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('--smtp-port PORT', Integer, 'Set the port of the smtp server') do |port|
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('--http-ip IP', 'Set the ip address of the http server') do |ip|
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('--http-port PORT', Integer, 'Set the port address of the http server') do |port|
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 self.run! options=nil
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 self.quit!
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 self.rescue_port port
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
@@ -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
@@ -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
@@ -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: #eee;
61
- color: #000;
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 #ccc; }
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: #4183C4; }
92
+ color: #4183c4; }
93
93
  body > header nav {
94
- border-left: 1px solid #ccc;
95
- border-right: 1px solid #fff; }
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 #fff;
107
- border-right: 1px solid #ccc;
108
- background: #eee;
109
- color: #666;
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: #fff; }
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: #fff;
127
- border-top: 1px solid #fff;
128
- border-bottom: 1px solid #ccc; }
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: #eee;
133
- color: #333; }
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: #666;
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 #fff; }
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: #666;
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 #999; }
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 #999;
198
- background: #ddd;
199
- color: #333;
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: #ddd; }
204
+ background-color: #dddddd; }
205
205
  #message > header .views .tab.selected {
206
- background: #fff;
207
- color: #000;
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 #999;
222
- background: #ddd;
223
- color: #333;
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: #fff;
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: #fff; }
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
- segments:
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-02 00:00:00 +08:00
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: &id010 !ruby/object:Gem::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: *id010
113
+ version_requirements: *id009
171
114
  - !ruby/object:Gem::Dependency
172
115
  name: compass
173
116
  prerelease: false
174
- requirement: &id011 !ruby/object:Gem::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: *id011
124
+ version_requirements: *id010
187
125
  - !ruby/object:Gem::Dependency
188
126
  name: coffee-script
189
127
  prerelease: false
190
- requirement: &id012 !ruby/object:Gem::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: *id012
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.6.2
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.