mailcatcher 0.5.12 → 0.6.0pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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