mailcatcher 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.md +40 -0
- data/bin/mailcatcher +28 -0
- data/lib/mail_catcher.rb +214 -0
- data/views/index.haml +139 -0
- metadata +158 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Samuel Cochran <sj26@sj26.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# MailCatcher
|
2
|
+
|
3
|
+
Catches mail and serves it through a dream.
|
4
|
+
|
5
|
+
MailCatcher runs a super simple SMTP server which catches any message sent to it to display in a web interface. Run mailcatcher, set your favourite app to deliver to smtp://127.0.0.1:1025 instead of your default SMTP server, then check out http://127.0.0.1:1080 to see the mail that's arrived so far.
|
6
|
+
|
7
|
+
## Features
|
8
|
+
|
9
|
+
* Catches all mail and stores it for display.
|
10
|
+
* Shows HTML, Plain Text and Source version of messages, as applicable.
|
11
|
+
* Rewrites HTML enabling display of embedded, inline images/etc. (currently very basic)
|
12
|
+
* Lists attachments and allows separate downloading of parts.
|
13
|
+
* Written super-simply in EventMachine, easy to dig in and change.
|
14
|
+
|
15
|
+
## Caveats
|
16
|
+
|
17
|
+
* For now you need to refresh the page to see new mail. Websockets support is coming soon to show new mail immediately.
|
18
|
+
* Mail proccessing is fairly basic but easily modified. If something doesn't work for you, fork and fix it or file an issue and let me know. Include the whole message you're having problems with.
|
19
|
+
* The interface is very basic and has not been tested on many browsers yet.
|
20
|
+
|
21
|
+
## TODO
|
22
|
+
|
23
|
+
* Command line options.
|
24
|
+
* Websockets for immediate mail viewing.
|
25
|
+
* Download link to view original message in mail client.
|
26
|
+
* Growl support.
|
27
|
+
* Test suite.
|
28
|
+
* Better organisation.
|
29
|
+
* Better interface. SproutCore?
|
30
|
+
* Add mail delivery on request, optionally multiple times.
|
31
|
+
* Forward mail to rendering service, maybe CampaignMonitor?
|
32
|
+
* Package as an app? Native interfaces?
|
33
|
+
|
34
|
+
## License
|
35
|
+
|
36
|
+
MailCatcher is released under the MIT License. See LICENSE.
|
37
|
+
|
38
|
+
## Dreams
|
39
|
+
|
40
|
+
For dream catching, try [this](http://goo.gl/kgbh).
|
data/bin/mailcatcher
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), '../lib'))
|
4
|
+
|
5
|
+
require 'mail_catcher'
|
6
|
+
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = 'Usage: mailcatcher [options]'
|
13
|
+
|
14
|
+
options[:verbose] = false
|
15
|
+
opts.on('-v', '--verbose', 'Be more verbose') do
|
16
|
+
options[:verbose] = true
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('-h', '--help', 'Display this help information') do
|
20
|
+
puts opts
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
end.parse!
|
24
|
+
|
25
|
+
puts 'Starting mail catcher'
|
26
|
+
puts '==> smtp://127.0.0.1:1025'
|
27
|
+
puts '==> http://127.0.0.1:1080'
|
28
|
+
MailCatcher.run
|
data/lib/mail_catcher.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'json'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'mail'
|
6
|
+
require 'thin'
|
7
|
+
require 'sqlite3'
|
8
|
+
require 'sinatra/base'
|
9
|
+
require 'sunshowers'
|
10
|
+
|
11
|
+
# Monkey-patch Sinatra to use Sunshowers
|
12
|
+
class Sinatra::Request < Rack::Request
|
13
|
+
include Sunshowers::WebSocket
|
14
|
+
end
|
15
|
+
|
16
|
+
module MailCatcher
|
17
|
+
def self.db
|
18
|
+
@@__db ||= begin
|
19
|
+
SQLite3::Database.new(':memory:', :results_as_hash => true, :type_translation => true).tap do |db|
|
20
|
+
begin
|
21
|
+
db.execute(<<-SQL)
|
22
|
+
CREATE TABLE mail (
|
23
|
+
id INTEGER PRIMARY KEY ASC,
|
24
|
+
sender TEXT,
|
25
|
+
recipients TEXT,
|
26
|
+
subject TEXT,
|
27
|
+
source BLOB,
|
28
|
+
size TEXT,
|
29
|
+
created_at DATETIME DEFAULT CURRENT_DATETIME
|
30
|
+
)
|
31
|
+
SQL
|
32
|
+
db.execute(<<-SQL)
|
33
|
+
CREATE TABLE part (
|
34
|
+
id INTEGER PRIMARY KEY ASC,
|
35
|
+
mail_id INTEGER NOT NULL,
|
36
|
+
cid TEXT,
|
37
|
+
type TEXT,
|
38
|
+
is_attachment INTEGER,
|
39
|
+
filename TEXT,
|
40
|
+
body BLOB,
|
41
|
+
size INTEGER,
|
42
|
+
created_at DATETIME DEFAULT CURRENT_DATETIME
|
43
|
+
)
|
44
|
+
SQL
|
45
|
+
rescue SQLite3::SQLException
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.subscribers
|
52
|
+
@@subscribers ||= []
|
53
|
+
end
|
54
|
+
|
55
|
+
class SmtpServer < EventMachine::Protocols::SmtpServer
|
56
|
+
def insert_message
|
57
|
+
@@insert_message ||= MailCatcher.db.prepare("INSERT INTO mail (sender, recipients, subject, source, size, created_at) VALUES (?, ?, ?, ?, ?, datetime('now'))")
|
58
|
+
end
|
59
|
+
|
60
|
+
def insert_part
|
61
|
+
@@insert_part ||= MailCatcher.db.prepare("INSERT INTO part (mail_id, cid, type, is_attachment, filename, body, size, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))")
|
62
|
+
end
|
63
|
+
|
64
|
+
def current_message
|
65
|
+
@current_message ||= OpenStruct.new
|
66
|
+
end
|
67
|
+
|
68
|
+
def receive_reset
|
69
|
+
@current_message = nil
|
70
|
+
true
|
71
|
+
end
|
72
|
+
|
73
|
+
def receive_sender(sender)
|
74
|
+
current_message.sender = sender
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def receive_recipient(recipient)
|
79
|
+
current_message.recipients ||= []
|
80
|
+
current_message.recipients << recipient
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def receive_data_chunk(lines)
|
85
|
+
current_message.source ||= ""
|
86
|
+
current_message.source += lines.join("\n")
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
def receive_message
|
91
|
+
mail = Mail.new(current_message.source)
|
92
|
+
result = insert_message.execute(current_message.sender, current_message.recipients.inspect, mail.subject, current_message.source, current_message.source.length)
|
93
|
+
mail_id = MailCatcher.db.last_insert_row_id
|
94
|
+
if mail.multipart?
|
95
|
+
mail.all_parts.each do |part|
|
96
|
+
body = part.body.to_s
|
97
|
+
insert_part.execute(mail_id, part.cid, part.mime_type, part.attachment? ? 1 : 0, part.filename, body, body.length)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
body = mail.body.to_s
|
101
|
+
insert_part.execute(mail_id, nil, mail.mime_type, 0, mail.filename, body, body.length)
|
102
|
+
end
|
103
|
+
puts "==> SMTP: Received message '#{mail.subject}' from '#{current_message.sender}'"
|
104
|
+
@current_message = nil
|
105
|
+
true
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class WebApp < Sinatra::Base
|
110
|
+
set :views, File.expand_path(File.join(File.dirname(__FILE__), '..', 'views'))
|
111
|
+
set :haml, {:format => :html5 }
|
112
|
+
|
113
|
+
get '/' do
|
114
|
+
haml :index
|
115
|
+
end
|
116
|
+
|
117
|
+
get '/mail' do
|
118
|
+
if latest = MailCatcher.db.query('SELECT created_at FROM mail ORDER BY created_at DESC LIMIT 1').next
|
119
|
+
last_modified latest["created_at"]
|
120
|
+
end
|
121
|
+
MailCatcher.db.query('SELECT id, sender, recipients, subject, size, created_at FROM mail ORDER BY created_at DESC').to_a.to_json
|
122
|
+
end
|
123
|
+
|
124
|
+
get '/mail/:id.json' do
|
125
|
+
mail_id = params[:id].to_i
|
126
|
+
message = MailCatcher.db.query('SELECT * FROM mail WHERE id = ? LIMIT 1', mail_id).to_a.first
|
127
|
+
if message
|
128
|
+
last_modified message["created_at"]
|
129
|
+
message["formats"] = ['eml']
|
130
|
+
message["formats"] << 'html' if MailCatcher.db.query('SELECT id FROM part WHERE mail_id = ? AND type = "text/html" LIMIT 1', mail_id).next
|
131
|
+
message["formats"] << 'txt' if MailCatcher.db.query('SELECT id FROM part WHERE mail_id = ? AND type = "text/plain" LIMIT 1', mail_id).next
|
132
|
+
message["attachments"] = MailCatcher.db.query('SELECT cid, type, filename, size FROM part WHERE mail_id = ? AND is_attachment = 1 ORDER BY filename ASC', mail_id).to_a.map do |attachment|
|
133
|
+
attachment.merge({"href" => "/mail/#{escape(params[:id])}/#{escape(attachment['cid'])}"})
|
134
|
+
end
|
135
|
+
message.to_json
|
136
|
+
else
|
137
|
+
not_found
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
get '/mail/:id.html' do
|
142
|
+
mail_id = params[:id].to_i
|
143
|
+
part = MailCatcher.db.query('SELECT body, created_at FROM part WHERE mail_id = ? AND type = "text/html" LIMIT 1', mail_id).to_a.first
|
144
|
+
if part
|
145
|
+
content_type 'text/html'
|
146
|
+
last_modified part["created_at"]
|
147
|
+
part["body"].gsub(/cid:([^'"> ]+)/, "#{mail_id}/\\1")
|
148
|
+
else
|
149
|
+
not_found
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
get '/mail/:id.txt' do
|
154
|
+
part = MailCatcher.db.query('SELECT body, created_at FROM part WHERE mail_id = ? AND type = "text/plain" LIMIT 1', params[:id].to_i).to_a.first
|
155
|
+
if part
|
156
|
+
content_type 'text/plain'
|
157
|
+
last_modified part["created_at"]
|
158
|
+
part["body"]
|
159
|
+
else
|
160
|
+
not_found
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
get '/mail/:id.eml' do
|
165
|
+
content_type 'text/plain'
|
166
|
+
message = MailCatcher.db.query('SELECT source, created_at FROM mail WHERE id = ? ORDER BY created_at DESC LIMIT 1', params[:id].to_i).to_a.first
|
167
|
+
if message
|
168
|
+
last_modified message["created_at"]
|
169
|
+
message["source"]
|
170
|
+
else
|
171
|
+
not_found
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
get '/mail/:id/:cid' do
|
176
|
+
result = MailCatcher.db.query('SELECT * FROM part WHERE mail_id = ?', params[:id].to_i)
|
177
|
+
part = result.find { |part| part["cid"] == params[:cid] }
|
178
|
+
if part
|
179
|
+
content_type part["type"]
|
180
|
+
attachment part["filename"] if part["is_attachment"] == 1
|
181
|
+
last_modified part["created_at"]
|
182
|
+
body part["body"].to_s
|
183
|
+
else
|
184
|
+
not_found
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
get '/mail/subscribe' do
|
189
|
+
return head 400 unless request.ws?
|
190
|
+
|
191
|
+
request.ws_handshake!
|
192
|
+
request.ws_io.each do |message|
|
193
|
+
ws_quit! if message == "goodbye"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
not_found do
|
198
|
+
"<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>"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.run(options = {})
|
203
|
+
options[:smtp_ip] ||= '127.0.0.1'
|
204
|
+
options[:smtp_port] ||= 1025
|
205
|
+
options[:http_ip] ||= '127.0.0.1'
|
206
|
+
options[:http_port] ||= 1080
|
207
|
+
|
208
|
+
Thin::Logging.silent = true
|
209
|
+
EM::run do
|
210
|
+
EM::start_server options[:smtp_ip], options[:smtp_port], SmtpServer
|
211
|
+
Thin::Server.start WebApp, options[:http_ip], options[:http_port]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
data/views/index.haml
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
!!!
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%title MailCatcher
|
5
|
+
%script{:src => "http://code.jquery.com/jquery-1.4.3.min.js"}
|
6
|
+
:javascript
|
7
|
+
var MailCatcher = {
|
8
|
+
refresh: function() {
|
9
|
+
console.log('Refreshing mail');
|
10
|
+
$.getJSON('/mail', function(mail) {
|
11
|
+
$.each(mail, function(i, message) {
|
12
|
+
var row = $('<tr />').attr('data-message-id', message.id.toString());
|
13
|
+
$.each(['sender', 'recipients', 'subject', 'created_at'], function (i, property) {
|
14
|
+
row.append($('<td />').text(message[property]));
|
15
|
+
});
|
16
|
+
$('#mail tbody').append(row);
|
17
|
+
});
|
18
|
+
});
|
19
|
+
},
|
20
|
+
load: function(id) {
|
21
|
+
var id = id || $('#mail tr.selected').attr('data-message-id');
|
22
|
+
|
23
|
+
if (id !== null) {
|
24
|
+
console.log('Loading message', id);
|
25
|
+
|
26
|
+
$('#mail tbody tr:not([data-message-id="'+id+'"])').removeClass('selected');
|
27
|
+
$('#mail tbody tr[data-message-id="'+id+'"]').addClass('selected');
|
28
|
+
$.getJSON('/mail/' + id + '.json', function(message) {
|
29
|
+
$('#message .received span').text(message.created_at);
|
30
|
+
$('#message .from span').text(message.sender);
|
31
|
+
$('#message .to span').text(message.recipients);
|
32
|
+
$('#message .subject span').text(message.subject);
|
33
|
+
$('#message .formats ul li').each(function(i, el) {
|
34
|
+
var $el = $(el),
|
35
|
+
format = $el.attr('data-message-format');
|
36
|
+
if ($.inArray(format, message.formats) >= 0) {
|
37
|
+
$el.show();
|
38
|
+
} else {
|
39
|
+
$el.hide();
|
40
|
+
}
|
41
|
+
})
|
42
|
+
if ($("#message .formats ul li.selected:not(:visible)")) {
|
43
|
+
$("#message .formats ul li.selected").removeClass("selected");
|
44
|
+
$("#message .formats ul li:visible:first").addClass("selected");
|
45
|
+
}
|
46
|
+
if (message.attachments.length > 0) {
|
47
|
+
console.log(message.attachments);
|
48
|
+
$('#message .attachments ul').empty();
|
49
|
+
$.each(message.attachments, function (i, attachment) {
|
50
|
+
$('#message .attachments ul').append($('<li>').append($('<a>').attr('href', attachment['href']).addClass(attachment['type'].split('/', 1)[0]).addClass(attachment['type'].replace('/', '-')).text(attachment['filename'])));
|
51
|
+
});
|
52
|
+
$('#message .attachments').show();
|
53
|
+
} else {
|
54
|
+
$('#message .attachments').hide();
|
55
|
+
}
|
56
|
+
MailCatcher.loadBody();
|
57
|
+
});
|
58
|
+
}
|
59
|
+
},
|
60
|
+
loadBody: function(id, format) {
|
61
|
+
var id = id || $('#mail tr.selected').attr('data-message-id');
|
62
|
+
var format = format || $('#message .formats ul li.selected').first().attr('data-message-format') || 'html';
|
63
|
+
|
64
|
+
$('#message .formats ul li[data-message-format="'+format+'"]').addClass('selected');
|
65
|
+
$('#message .formats ul li:not([data-message-format="'+format+'"])').removeClass('selected');
|
66
|
+
|
67
|
+
if (id != undefined && id !== null) {
|
68
|
+
console.log('Loading message', id, 'in format', format);
|
69
|
+
|
70
|
+
$('#message iframe').attr('src', '/mail/' + id + '.' + format);
|
71
|
+
}
|
72
|
+
}
|
73
|
+
};
|
74
|
+
|
75
|
+
$('#mail tr').live('click', function() {
|
76
|
+
MailCatcher.load($(this).attr('data-message-id'));
|
77
|
+
});
|
78
|
+
|
79
|
+
$('#message .formats ul li').live('click', function() {
|
80
|
+
MailCatcher.loadBody($('#mail tr.selected').attr('data-message-id'), $(this).attr('data-message-format'));
|
81
|
+
});
|
82
|
+
|
83
|
+
$(MailCatcher.refresh);
|
84
|
+
:css
|
85
|
+
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin:0px; padding:0px; border:0px; outline:0px; font-weight:inherit; font-style:inherit; font-size:100%; font-family:inherit; vertical-align:baseline; }
|
86
|
+
body { line-height:1; color:black; background:white; font-size:12px; font-family:Helvetica, Arial, sans-serif; }
|
87
|
+
ol, ul { list-style:none; }
|
88
|
+
table { border-collapse:separate; border-spacing:0px; }
|
89
|
+
caption, th, td { text-align:left; font-weight:normal; }
|
90
|
+
|
91
|
+
#mail { height: 10em; overflow: scroll; }
|
92
|
+
#mail table { width: 100%; }
|
93
|
+
#mail table thead tr { background: #ccc; }
|
94
|
+
#mail table thead tr th { padding: .25em; font-weight: bold; }
|
95
|
+
#mail table tbody tr:nth-child(even) { background: #eee; }
|
96
|
+
#mail table tbody tr.selected { background: Highlight; color: HighlightText; }
|
97
|
+
#mail table tbody tr td { padding: .25em; }
|
98
|
+
#message .metadata { padding: 1em; background: #ccc; }
|
99
|
+
#message .metadata div { padding: .25em;; }
|
100
|
+
#message .metadata div label { display: inline-block; width: 8em; text-align: right; font-weight: bold; }
|
101
|
+
#message .metadata .attachments { display: none; }
|
102
|
+
#message .metadata .attachments ul { display: inline; }
|
103
|
+
#message .metadata .attachments ul li { display: inline-block; }
|
104
|
+
#message .formats ul { background: #ccc; border-bottom: 1px solid #666; padding: 0 .5em; }
|
105
|
+
#message .formats ul li { display: inline-block; padding: .5em; border: solid #666; border-width: 1px 1px 0 1px; }
|
106
|
+
#message .formats ul li.selected { background: white; height: 13px; margin-bottom: -1px; }
|
107
|
+
#message iframe { width: 100%; height: 42em; }
|
108
|
+
%body
|
109
|
+
#mail
|
110
|
+
%table
|
111
|
+
%thead
|
112
|
+
%th From
|
113
|
+
%th To
|
114
|
+
%th Subject
|
115
|
+
%th Received
|
116
|
+
%tbody
|
117
|
+
#message
|
118
|
+
.metadata
|
119
|
+
.received
|
120
|
+
%label Received
|
121
|
+
%span
|
122
|
+
.from
|
123
|
+
%label From
|
124
|
+
%span
|
125
|
+
.to
|
126
|
+
%label To
|
127
|
+
%span
|
128
|
+
.subject
|
129
|
+
%label Subject
|
130
|
+
%span
|
131
|
+
.attachments
|
132
|
+
%label Attachments
|
133
|
+
%ul
|
134
|
+
.formats
|
135
|
+
%ul
|
136
|
+
%li.selected{'data-message-format' => 'html'} HTML
|
137
|
+
%li{'data-message-format' => 'txt'} Plain Text
|
138
|
+
%li{'data-message-format' => 'eml'} Source
|
139
|
+
%iframe
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mailcatcher
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
version: 0.1.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Samuel Cochran
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-10-25 00:00:00 +08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: eventmachine
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: json
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mail
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id003
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: thin
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
type: :runtime
|
71
|
+
version_requirements: *id004
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: sqlite3-ruby
|
74
|
+
prerelease: false
|
75
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
type: :runtime
|
84
|
+
version_requirements: *id005
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: sinatra
|
87
|
+
prerelease: false
|
88
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
type: :runtime
|
97
|
+
version_requirements: *id006
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: sunshowers
|
100
|
+
prerelease: false
|
101
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
version: "0"
|
109
|
+
type: :runtime
|
110
|
+
version_requirements: *id007
|
111
|
+
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"
|
112
|
+
email: sj26@sj26.com
|
113
|
+
executables:
|
114
|
+
- mailcatcher
|
115
|
+
extensions: []
|
116
|
+
|
117
|
+
extra_rdoc_files: []
|
118
|
+
|
119
|
+
files:
|
120
|
+
- bin/mailcatcher
|
121
|
+
- lib/mail_catcher.rb
|
122
|
+
- views/index.haml
|
123
|
+
- README.md
|
124
|
+
- LICENSE
|
125
|
+
has_rdoc: true
|
126
|
+
homepage: http://github.com/sj26/mailcatcher
|
127
|
+
licenses: []
|
128
|
+
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
none: false
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
segments:
|
140
|
+
- 0
|
141
|
+
version: "0"
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
none: false
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
version: "0"
|
150
|
+
requirements: []
|
151
|
+
|
152
|
+
rubyforge_project:
|
153
|
+
rubygems_version: 1.3.7
|
154
|
+
signing_key:
|
155
|
+
specification_version: 3
|
156
|
+
summary: Runs an SMTP server, catches and displays email in a web interface.
|
157
|
+
test_files: []
|
158
|
+
|