mailcatcher 0.5.12 → 0.6.0pre1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 087ab236a57889861232c36337622a2c491f015e
4
- data.tar.gz: 2025e9693e21dae93e87d28261ae520ea3b1d4f8
3
+ metadata.gz: 20a21ec3f69fd47bbc8c269689998ace77da0c1b
4
+ data.tar.gz: f55e1936c3fb237909fb0bf1bc1282af1c709175
5
5
  SHA512:
6
- metadata.gz: 96f670bb993dc0f567da90089a88effdec7ec1f4fcd30d85e7f0d63a9ee18446f4edd0b6ebbb48a3062d0f7bd0cfb9fe41cfd0edea05d53864cfc5930b034121
7
- data.tar.gz: 42daf4cd55e174e109c08c041261ac3cd5597b57fdcb5a6e632a31e838f8a4b7f828a24871e18833aa5de6635848065d150ee93f7eaab3f9aef0df0ed6f19b31
6
+ metadata.gz: 693e35eb5cec71175f37e7c7c57a08a591985a65284c71c4a8684e7c020114f9f7733b522f6df404fca1ba5a922b098836fb7b02497ed25e93ca834b06f3d216
7
+ data.tar.gz: 8e474863a1a3bd887322bb4b9007aba2c14a44d5a3b783cc3384adc5df7ca1f89d07df069e0ed040a8315d47791568b22fc05fff54dbdcf15633308ea4fed0ba
Binary file
data.tar.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -10,13 +10,11 @@ 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 open links in a new window. (currently very basic)
14
- * Can send HTML for analysis by [Fractal][fractal].
13
+ * Rewrites HTML enabling display of embedded, inline images/etc and open links in a new window.
15
14
  * Lists attachments and allows separate downloading of parts.
16
15
  * Download original email to view in your native mail client(s).
17
16
  * Command line options to override the default SMTP/HTTP IP and port settings.
18
17
  * Mail appears instantly if your browser supports [WebSockets][websockets], otherwise updates every thirty seconds.
19
- * Growl notifications when you receive a new message.
20
18
  * Runs as a daemon run in the background.
21
19
  * Sendmail-analogue command, `catchmail`, makes [using mailcatcher from PHP][withphp] a lot easier.
22
20
  * Written super-simply in EventMachine, easy to dig in and change.
@@ -29,7 +27,7 @@ MailCatcher runs a super simple SMTP server which catches any message sent to it
29
27
  3. Go to http://localhost:1080/
30
28
  4. Send mail through smtp://localhost:1025
31
29
 
32
- The brave can get the source from [the GitHub repository][mailcatcher-github].
30
+ Use `mailcatcher --help` to see the command line options. The brave can get the source from [the GitHub repository][mailcatcher-github].
33
31
 
34
32
  ### Bundler
35
33
 
@@ -46,7 +44,7 @@ Under RVM your mailcatcher command may only be available under the ruby you inst
46
44
 
47
45
  ### Rails
48
46
 
49
- To set up your rails app, I recommend adding this to your `environment/development.rb`:
47
+ To set up your rails app, I recommend adding this to your `environments/development.rb`:
50
48
 
51
49
  config.action_mailer.delivery_method = :smtp
52
50
  config.action_mailer.smtp_settings = { :address => "localhost", :port => 1025 }
@@ -55,14 +53,31 @@ To set up your rails app, I recommend adding this to your `environment/developme
55
53
 
56
54
  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:
57
55
 
58
- sendmail_path = /usr/bin/env catchmail
56
+ sendmail_path = /usr/bin/env catchmail -f some@from.address
59
57
 
60
- You can do this in an [Apache htaccess file](http://php.net/manual/en/configuration.changes.php) or general configuration like so:
58
+ You can do this in your [Apache configuration](http://php.net/manual/en/configuration.changes.php) like so:
61
59
 
62
- php_value sendmail_path "/usr/bin/env catchmail"
60
+ php_admin_value sendmail_path "/usr/bin/env catchmail -f some@from.address"
63
61
 
64
62
  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`.
65
63
 
64
+ If starting `mailcatcher` on alternative SMTP IP and/or port with parameters like `--smtp-ip 192.168.0.1 --smtp-port 10025`, add the same parameters to your `catchmail` command:
65
+
66
+ sendmail_path = /usr/bin/env catchmail --smtp-ip 192.160.0.1 --smtp-port 10025 -f some@from.address
67
+
68
+ ### Django
69
+
70
+ For use in Django, simply add the following configuration to your projects' settings.py
71
+
72
+ ```python
73
+ if DEBUG:
74
+ EMAIL_HOST = '127.0.0.1'
75
+ EMAIL_HOST_USER = ''
76
+ EMAIL_HOST_PASSWORD = ''
77
+ EMAIL_PORT = 1025
78
+ EMAIL_USE_TLS = False
79
+ ```
80
+
66
81
  ### API
67
82
 
68
83
  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`.
@@ -75,10 +90,9 @@ A fairly RESTful URL schema means you can download a list of messages in JSON fr
75
90
  ## TODO
76
91
 
77
92
  * Add mail delivery on request, optionally multiple times.
78
- * Better Growl support in MacRuby and RubyCocoa with click notifications which takes you to the received message.
79
93
  * An API-compatible nodejs version, for fun and profit (and non-ruby npm users).
80
94
  * Test suite.
81
- * Compatibility testing against CampaignMonitor's [design guidelines](http://www.campaignmonitor.com/design-guidelines/) and [CSS support matrix](http://www.campaignmonitor.com/design-guidelines/).
95
+ * Compatibility testing against CampaignMonitor's [design guidelines](http://www.campaignmonitor.com/design-guidelines/) and [CSS support matrix](http://www.campaignmonitor.com/css/).
82
96
  * Forward mail to rendering service, maybe CampaignMonitor?
83
97
  * Package as an app? Native interfaces? HotCocoa?
84
98
 
@@ -101,7 +115,6 @@ Copyright © 2010-2011 Samuel Cochran (sj26@sj26.com). Released under the MIT Li
101
115
  For dream catching, try [this](http://goo.gl/kgbh). OR [THIS](http://www.nyanicorn.com), OMG.
102
116
 
103
117
  [donate]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=522WUPLRWUSKE
104
- [fractal]: http://getfractal.com
105
118
  [license]: https://github.com/sj26/mailcatcher/blob/master/LICENSE
106
119
  [mailcatcher-github]: https://github.com/sj26/mailcatcher
107
120
  [mailcatcher-issues]: https://github.com/sj26/mailcatcher/issues
@@ -13,7 +13,7 @@ options = {:smtp_ip => '127.0.0.1', :smtp_port => 1025}
13
13
 
14
14
  OptionParser.new do |parser|
15
15
  parser.banner = <<-BANNER.gsub /^ +/, ""
16
- Usage: catchmail [options]
16
+ Usage: catchmail [options] [recipient ...]
17
17
  sendmail-like interface to forward mail to MailCatcher.
18
18
  BANNER
19
19
 
@@ -33,6 +33,17 @@ OptionParser.new do |parser|
33
33
  options[:from] = from
34
34
  end
35
35
 
36
+ parser.on('-oi', 'Ignored option -oi') do |ignored|
37
+ end
38
+ parser.on('-t', 'Ignored option -t') do |ignored|
39
+ end
40
+ parser.on('-q', 'Ignored option -q') do |ignored|
41
+ end
42
+
43
+ parser.on('-x', '--no-exit', 'Can\'t exit from the application') do
44
+ options[:no_exit] = true
45
+ end
46
+
36
47
  parser.on('-h', '--help', 'Display this help information') do
37
48
  puts parser
38
49
  exit!
@@ -45,6 +56,16 @@ Mail.defaults do
45
56
  :port => options[:smtp_port]
46
57
  end
47
58
 
48
- message = Mail.new ARGF.read
59
+ message = Mail.new($stdin.read)
60
+
49
61
  message.return_path = options[:from] if options[:from]
62
+
63
+ ARGV.each do |recipient|
64
+ if message.to.nil?
65
+ message.to = recipient
66
+ else
67
+ message.to << recipient
68
+ end
69
+ end
70
+
50
71
  message.deliver
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'mail_catcher'
4
+
4
5
  MailCatcher.run!
@@ -1,14 +1,19 @@
1
- require 'active_support/core_ext'
2
- require 'eventmachine'
3
- require 'open3'
4
- require 'optparse'
5
- require 'rbconfig'
6
- require 'thin'
1
+ require "open3"
2
+ require "optparse"
3
+ require "rbconfig"
7
4
 
8
- require 'mail_catcher/version'
5
+ require "active_support/all"
6
+ require "eventmachine"
7
+ require "thin"
8
+
9
+ require "mail_catcher/events"
10
+ require "mail_catcher/mail"
11
+ require "mail_catcher/smtp"
12
+ require "mail_catcher/web"
13
+ require "mail_catcher/version"
9
14
 
10
15
  module MailCatcher extend self
11
- def which command
16
+ def which(command)
12
17
  not windows? and Open3.popen3 'which', 'command' do |stdin, stdout, stderr|
13
18
  return stdout.read.chomp.presence
14
19
  end
@@ -26,14 +31,6 @@ module MailCatcher extend self
26
31
  mac? and const_defined? :MACRUBY_VERSION
27
32
  end
28
33
 
29
- def growlnotify?
30
- which "growlnotify"
31
- end
32
-
33
- def growl?
34
- growlnotify?
35
- end
36
-
37
34
  def browse?
38
35
  windows? or which "open"
39
36
  end
@@ -46,19 +43,27 @@ module MailCatcher extend self
46
43
  end
47
44
  end
48
45
 
49
- @defaults = {
46
+ @@defaults = {
50
47
  :smtp_ip => '127.0.0.1',
51
48
  :smtp_port => '1025',
52
49
  :http_ip => '127.0.0.1',
53
50
  :http_port => '1080',
54
51
  :verbose => false,
55
52
  :daemon => !windows?,
56
- :growl => growlnotify?,
57
53
  :browse => false,
54
+ :quit => true,
58
55
  }
59
56
 
57
+ def options
58
+ @@options
59
+ end
60
+
61
+ def quittable?
62
+ options[:quit]
63
+ end
64
+
60
65
  def parse! arguments=ARGV, defaults=@defaults
61
- @defaults.dup.tap do |options|
66
+ @@defaults.dup.tap do |options|
62
67
  OptionParser.new do |parser|
63
68
  parser.banner = "Usage: mailcatcher [options]"
64
69
  parser.version = VERSION
@@ -83,16 +88,14 @@ module MailCatcher extend self
83
88
  options[:http_port] = port
84
89
  end
85
90
 
91
+ parser.on("--no-quit", "Don't allow quitting the process") do
92
+ options[:quit] = false
93
+ end
94
+
86
95
  if mac?
87
- parser.on("--[no-]growl", "Growl to the local machine when a message arrives") do |growl|
88
- if growl and not growlnotify?
89
- puts "You'll need to install growlnotify from the Growl installer."
90
- puts
91
- puts "See: http://growl.info/extras.php#growlnotify"
92
- exit!
93
- end
94
-
95
- options[:growl] = growl
96
+ parser.on("--[no-]growl") do |growl|
97
+ puts "Growl is no longer supported"
98
+ exit -2
96
99
  end
97
100
  end
98
101
 
@@ -114,7 +117,7 @@ module MailCatcher extend self
114
117
 
115
118
  parser.on('-h', '--help', 'Display this help information') do
116
119
  puts parser
117
- exit!
120
+ exit
118
121
  end
119
122
  end.parse!
120
123
  end
@@ -122,19 +125,24 @@ module MailCatcher extend self
122
125
 
123
126
  def run! options=nil
124
127
  # If we are passed options, fill in the blanks
125
- options &&= @defaults.merge options
128
+ options &&= options.reverse_merge @@defaults
126
129
  # Otherwise, parse them from ARGV
127
130
  options ||= parse!
128
131
 
132
+ # Stash them away for later
133
+ @@options = options
134
+
135
+ # If we're running in the foreground sync the output.
136
+ unless options[:daemon]
137
+ $stdout.sync = $stderr.sync = true
138
+ end
139
+
129
140
  puts "Starting MailCatcher"
130
141
 
131
- Thin::Logging.silent = true
142
+ Thin::Logging.silent = (ENV["MAILCATCHER_ENV"] != "development")
132
143
 
133
144
  # One EventMachine loop...
134
145
  EventMachine.run do
135
- # Get our lion on if asked
136
- MailCatcher::Growl.start if options[:growl]
137
-
138
146
  smtp_url = "smtp://#{options[:smtp_ip]}:#{options[:smtp_port]}"
139
147
  http_url = "http://#{options[:http_ip]}:#{options[:http_port]}"
140
148
 
@@ -147,7 +155,7 @@ module MailCatcher extend self
147
155
  # Let Thin set itself up inside our EventMachine loop
148
156
  # (Skinny/WebSockets just works on the inside)
149
157
  rescue_port options[:http_port] do
150
- Thin::Server.start options[:http_ip], options[:http_port], Web
158
+ Thin::Server.start(options[:http_ip], options[:http_port], Web)
151
159
  puts "==> #{http_url}"
152
160
  end
153
161
 
@@ -161,7 +169,11 @@ module MailCatcher extend self
161
169
  # Daemonize, if we should, but only after the servers have started.
162
170
  if options[:daemon]
163
171
  EventMachine.next_tick do
164
- puts "*** MailCatcher runs as a daemon by default. Go to the web interface to quit."
172
+ if quittable?
173
+ puts "*** MailCatcher runs as a daemon by default. Go to the web interface to quit."
174
+ else
175
+ puts "*** MailCatcher is now running as a daemon that cannot be quit."
176
+ end
165
177
  Process.daemon
166
178
  end
167
179
  end
@@ -182,16 +194,10 @@ protected
182
194
  rescue RuntimeError
183
195
  if $!.to_s =~ /\bno acceptor\b/
184
196
  puts "~~> ERROR: Something's using port #{port}. Are you already running MailCatcher?"
185
- exit(-1)
197
+ exit -1
186
198
  else
187
199
  raise
188
200
  end
189
201
  end
190
202
  end
191
203
  end
192
-
193
- require 'mail_catcher/events'
194
- require 'mail_catcher/growl'
195
- require 'mail_catcher/mail'
196
- require 'mail_catcher/smtp'
197
- require 'mail_catcher/web'
@@ -1,5 +1,7 @@
1
- require 'eventmachine'
1
+ require "eventmachine"
2
2
 
3
- module MailCatcher::Events
4
- MessageAdded = EventMachine::Channel.new
3
+ module MailCatcher
4
+ module Events
5
+ MessageAdded = EventMachine::Channel.new
6
+ end
5
7
  end
@@ -1,12 +1,12 @@
1
- require 'active_support/json'
2
- require 'mail'
3
- require 'sqlite3'
4
- require 'eventmachine'
1
+ require "active_support/json"
2
+ require "eventmachine"
3
+ require "mail"
4
+ require "sqlite3"
5
5
 
6
6
  module MailCatcher::Mail extend self
7
7
  def db
8
8
  @__db ||= begin
9
- SQLite3::Database.new(':memory:', :type_translation => true).tap do |db|
9
+ SQLite3::Database.new(":memory:", :type_translation => true).tap do |db|
10
10
  db.execute(<<-SQL)
11
11
  CREATE TABLE message (
12
12
  id INTEGER PRIMARY KEY ASC,
@@ -41,7 +41,7 @@ module MailCatcher::Mail extend self
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
- @add_message_query.execute(message[:sender], message[:recipients].to_json, mail.subject, message[:source], mail.mime_type || 'text/plain', message[:source].length)
44
+ @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
46
46
  parts = mail.all_parts
47
47
  parts = [mail] if parts.empty?
@@ -49,7 +49,7 @@ module MailCatcher::Mail extend self
49
49
  body = part.body.to_s
50
50
  # Only parts have CIDs, not mail
51
51
  cid = part.cid if part.respond_to? :cid
52
- add_message_part(message_id, cid, part.mime_type || 'text/plain', part.attachment? ? 1 : 0, part.filename, part.charset, body, body.length)
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
@@ -69,7 +69,7 @@ module MailCatcher::Mail extend self
69
69
  end
70
70
 
71
71
  def messages
72
- @messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at DESC"
72
+ @messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at, id ASC"
73
73
  @messages_query.execute.map do |row|
74
74
  Hash[row.fields.zip(row)].tap do |message|
75
75
  message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
@@ -87,7 +87,7 @@ module MailCatcher::Mail extend self
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
- (!!@message_has_html_query.execute(id).next) || ['text/html', 'application/xhtml+xml'].include?(message(id)["type"])
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)
@@ -126,7 +126,7 @@ module MailCatcher::Mail extend self
126
126
  part ||= message_part_type(message_id, "application/xhtml+xml")
127
127
  part ||= begin
128
128
  message = message(message_id)
129
- message if message.present? and ['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
 
@@ -135,7 +135,7 @@ module MailCatcher::Mail extend self
135
135
  end
136
136
 
137
137
  def message_part_cid(message_id, cid)
138
- @message_part_cid_query ||= db.prepare 'SELECT * FROM message_part WHERE message_id = ?'
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|
140
140
  Hash[row.fields.zip(row)]
141
141
  end.find do |part|
@@ -144,16 +144,16 @@ module MailCatcher::Mail extend self
144
144
  end
145
145
 
146
146
  def delete!
147
- @delete_messages_query ||= db.prepare 'DELETE FROM message'
148
- @delete_message_parts_query ||= db.prepare 'DELETE FROM message_part'
147
+ @delete_messages_query ||= db.prepare "DELETE FROM message"
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
 
154
154
  def delete_message!(message_id)
155
- @delete_messages_query ||= db.prepare 'DELETE FROM message WHERE id = ?'
156
- @delete_message_parts_query ||= db.prepare 'DELETE FROM message_part WHERE message_id = ?'
155
+ @delete_messages_query ||= db.prepare "DELETE FROM message WHERE id = ?"
156
+ @delete_message_parts_query ||= db.prepare "DELETE FROM message_part WHERE message_id = ?"
157
157
  @delete_messages_query.execute(message_id) and
158
158
  @delete_message_parts_query.execute(message_id)
159
159
  end
@@ -1,4 +1,6 @@
1
- require 'eventmachine'
1
+ require "eventmachine"
2
+
3
+ require "mail_catcher/mail"
2
4
 
3
5
  class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
4
6
  # We override EM's mail from processing to allow multiple mail-from commands
@@ -34,7 +36,9 @@ class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
34
36
 
35
37
  def receive_data_chunk(lines)
36
38
  current_message[:source] ||= ""
37
- current_message[:source] << lines.join("\n")
39
+ lines.each do |line|
40
+ current_message[:source] << line << "\r\n"
41
+ end
38
42
  true
39
43
  end
40
44
 
@@ -1,3 +1,3 @@
1
1
  module MailCatcher
2
- VERSION = "0.5.12"
2
+ VERSION = "0.6.0pre1"
3
3
  end