├── .DS_Store ├── .gemtest ├── .ruby-gemset ├── .ruby-version ├── Gemfile ├── History.txt ├── Manifest.txt ├── README.txt ├── Rakefile ├── bin └── mailtrap ├── doc ├── classes │ ├── Mailtrap.html │ ├── Mailtrap.src │ │ ├── M000001.html │ │ ├── M000002.html │ │ ├── M000003.html │ │ └── M000004.html │ └── Mailtrap │ │ ├── LogParser.html │ │ └── LogParser.src │ │ └── M000005.html ├── created.rid ├── files │ ├── History_txt.html │ ├── README_txt.html │ ├── bin │ │ └── mailtrap.html │ └── lib │ │ ├── mailtrap │ │ └── log_parser_rb.html │ │ └── mailtrap_rb.html ├── fr_class_index.html ├── fr_file_index.html ├── fr_method_index.html ├── index.html └── rdoc-style.css ├── lib ├── mailshovel.rb ├── mailtrap.rb └── mailtrap │ └── log_parser.rb ├── mailtrap.gemspec ├── spec ├── mailtrap │ ├── log_parser_spec.rb │ └── sample_logs │ │ ├── sample.log │ │ └── sample_empty.log └── spec.opts └── test ├── mailshovel_test.rb └── test_mailtrap.rb /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmower/mailtrap/67d78d443f1ac6656e6369b95f544c2470d191f6/.DS_Store -------------------------------------------------------------------------------- /.gemtest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmower/mailtrap/67d78d443f1ac6656e6369b95f544c2470d191f6/.gemtest -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | mailtrap 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-1.9.3-p392 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | ruby '1.9.3' 3 | 4 | gemspec 5 | 6 | group :test do 7 | gem 'hoe' 8 | gem 'hoe-gemspec' 9 | gem 'hoe-git' 10 | gem 'rspec' 11 | gem 'minitest', '~> 4.7', :platforms => :ruby_19 12 | gem 'rake' 13 | gem 'rdoc' 14 | gem 'shoulda' 15 | gem 'mocha' 16 | gem 'fakefs' 17 | gem 'tmail', '~> 1.2.3' 18 | gem 'debugger' 19 | end 20 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | === 0.2.3.20130709144258 / 2013-07-09 2 | * Github fork pre-release (trinitronx/mailtrap) 3 | * Many fixes, gem development updates + Better SMTP client support! 4 | 5 | * 5 bug fixes: 6 | 7 | * Fixed Gemfile dev/test dependencies ^_^ 8 | * If client uses HELO, send 250 OK first so client goes ahead 9 | * Fixed a hang when using ssmtp + mailtrap 10 | * Fix client out of bounds error: Delete files only after QUIT when DELE command is received (by Luke Redpath) 11 | * Fixed broken #connect method, use local not instance vars 12 | 13 | * 4 major enhancements: 14 | 15 | * Added support for AUTH LOGIN, NOOP, and other easy SMTP verbs 16 | * RSpec tests all PASSING! 17 | * Added Mailtrap::LogParser for turning Mailtrap log files into an array of TMail::Mail objects - useful for inspecting the file 18 | for testing purposes. (by Ashley Moran) 19 | * Migrated from abandonware TMail to Mail (by Larry Siden) 20 | 21 | * 9 minor enhancements: 22 | 23 | * Cleaned up Rakefile to use newer Hoe syntax 24 | * Cleaned up many deprecation warnings 25 | * Added .gemtest file to suppress bundler error 26 | * Using pattern instead of list of files for RSpec 27 | * Re-generated gemspec & boilerplate stuff using Hoe + flavorjones/hoe-gemspec 28 | * Tests added by Luke Redpath 29 | * Added a gemspec so we can use bundler 30 | * Pass Mailshovel arguments in the correct order (by Luke Redpath) 31 | * Updated History file with all back versions & update descriptions 32 | 33 | == 0.2.2 / 2008-03-31 34 | * Github pre-release 35 | * Mailshovel added by Gwyn Morfey 36 | * Mailtrap writes messages as separate files to msg_dir so that we can serve them out by POP3. 37 | * Default --msg_dir: /var/tmp/mailtrap.log 38 | * Default --pop3_host: localhost 39 | * Default --pop3_port: 1100 40 | 41 | == 0.2.1 / 2008-04-03 42 | 43 | * Rubyforge & RubyGems release 44 | * Moved to 'SimplyRuby' RubyForge project 45 | * Log beginning & end of each message 46 | * Log mailtrap start to msgfile 47 | * README improvements 48 | 49 | == 0.2.0 / 2007-10-03 50 | 51 | * RubyGems release 52 | * Append to the end of the messages file 53 | * Mailtrap --msgdir changed to --file (Default: /var/tmp/mailtrap.log) 54 | * Default --host: localhost 55 | * Default --port: 2525 56 | * Default --once: false 57 | * Added comments 58 | * README improvements 59 | 60 | == 0.1.0 / 2007-10-03 61 | 62 | * 1 major enhancement 63 | * Birthday! 64 | 65 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | History.txt 2 | Manifest.txt 3 | README.txt 4 | Rakefile 5 | bin/mailtrap 6 | lib/mailtrap.rb 7 | lib/mailshovel.rb 8 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | = mailtrap 2 | * http://matt.blogs.it/ 3 | 4 | by Matt Mower 5 | modified (and Mailshovel added) by Gwyn Morfey 6 | modified (and updated) by James Cuzella 7 | 8 | == DESCRIPTION: 9 | 10 | Mailtrap is a mock SMTP server for use in Rails development. This package also includes Mailshovel, a mock POP3 server that works with Mailtrap. You can configure your mail client (eg Mail.App) to connect to Mailshovel, and then manage messages that ActionMailer has "sent" using its GUI. 11 | 12 | Mailtrap waits on your chosen port for a client to connect and talks _just enough_ SMTP protocol for ActionMailer to successfully deliver its message. 13 | 14 | Mailtrap makes *no* attempt to actually deliver messages and, instead, writes them into a series of files which are read by Mailshovel. 15 | 16 | You can configure the hostname (default: localhost) and port (default: 2525) for the server and also where the messages get written (default: /var/tmp/mailtrap.log). 17 | 18 | == FEATURES/PROBLEMS: 19 | 20 | * Lightweight, no setup 21 | * Runs as a daemon with start, stop, etc.. 22 | * Tested with ActionMailer's SMTP delivery method 23 | * Very, very, dumb ... might not work with an arbitrary SMTP client 24 | 25 | == SYNOPSIS: 26 | 27 | To use the defaults host:localhost, port:2525 and port:1100, file:/var/log/mailtrap.log 28 | 29 | * mailtrap start 30 | 31 | This will start both mailtrap and mailshovel. 32 | 33 | Customise startup: 34 | 35 | * sudo mailtrap start --host my.host --smtp_port 25 --pop3_port 110 --once --file=/var/log/messages.txt --msg_dir=/tmp/msgs 36 | (sudo because you want to use restricted port 25) 37 | 38 | For more info: 39 | 40 | * mailtrap --help (to see Daemonization options) 41 | * mailtrap start --help (to see Mailtrap options) 42 | 43 | == REQUIREMENTS: 44 | 45 | * Hoe rubygem 46 | * Rubygems rubygem 47 | * Daemons rubygem 48 | * Trollop rubygem 49 | 50 | All these are automatically installed if you use gem install -y 51 | 52 | == INSTALL: 53 | 54 | * sudo gem install -y mailtrap 55 | 56 | == LICENSE: 57 | 58 | (The MIT License) 59 | 60 | Copyright (c) 2007 Matt Mower, Software Heretics 61 | Copyright (c) 2013 James Cuzella 62 | 63 | Permission is hereby granted, free of charge, to any person obtaining 64 | a copy of this software and associated documentation files (the 65 | 'Software'), to deal in the Software without restriction, including 66 | without limitation the rights to use, copy, modify, merge, publish, 67 | distribute, sublicense, and/or sell copies of the Software, and to 68 | permit persons to whom the Software is furnished to do so, subject to 69 | the following conditions: 70 | 71 | The above copyright notice and this permission notice shall be 72 | included in all copies or substantial portions of the Software. 73 | 74 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 75 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 76 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 77 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 78 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 79 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 80 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 81 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | require 'rubygems' 4 | require 'hoe' 5 | require 'rspec/core/rake_task' 6 | #require './lib/mailtrap.rb' 7 | #require './lib/mailshovel.rb' 8 | # Hoe.plugin :rcov 9 | Hoe.plugin :gemspec 10 | Hoe.plugin :git 11 | 12 | Hoe.spec "mailtrap" do 13 | #self.rubyforge_name = 'simplyruby' ## Rubyforge shut down 14 | developer 'Matt Mower', 'self@mattmower.com' 15 | developer 'James Cuzella', 'james.cuzella@lyraphase.com' 16 | #summary 'Mailtrap is a mock SMTP server for use in Rails development' 17 | #description self.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1] 18 | #url self.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1] 19 | #changes self.paragraphs_of('History.txt', 0..1).join("\n\n") 20 | #remote_rdoc_dir 'mailtrap' 21 | extra_deps << ['daemons','>= 1.0.8'] 22 | extra_deps << ['trollop','>= 1.7'] 23 | extra_deps << ['mail','~> 2.3.0'] 24 | 25 | extra_dev_deps << ["rspec", "~> 3.0"] 26 | extra_dev_deps << ["simplecov", "~> 0.9"] 27 | extra_dev_deps << ["bundler", "~> 1.6"] 28 | extra_dev_deps << ["rake", "~> 10.0"] 29 | extra_dev_deps << ["hoe", "~> 3.13"] 30 | end 31 | 32 | namespace :spec do 33 | desc "Run the specs under spec" 34 | RSpec::Core::RakeTask.new 'all' do |t| 35 | t.rspec_opts = [ '--options', "spec/spec.opts" ] 36 | t.pattern = 'spec/**/*_spec.rb' 37 | end 38 | 39 | desc "Run the specs under spec in specdoc format" 40 | RSpec::Core::RakeTask.new 'doc' do |t| 41 | t.rspec_opts = [ '--format', "documentation" ] 42 | t.pattern = 'spec/**/*_spec.rb' 43 | end 44 | 45 | desc "Run the specs in HTML format" 46 | RSpec::Core::RakeTask.new 'html' do |t| 47 | t.rspec_opts = [ '--format', "html" ] 48 | t.pattern = 'spec/**/*_spec.rb' 49 | end 50 | end 51 | 52 | desc "Run the default spec task" 53 | task :spec => :"spec:all" 54 | 55 | # vim: syntax=Ruby 56 | -------------------------------------------------------------------------------- /bin/mailtrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Run the mailtrap server 4 | # 5 | 6 | require 'rubygems' 7 | require 'daemons' 8 | require 'mailtrap' 9 | require 'mailshovel' 10 | 11 | opts = Trollop::options do 12 | opt :smtp_host, "The host SMTP clients will connect to", :default => 'localhost' 13 | opt :smtp_port, "The port SMTP clients will connect to", :default => 2525 14 | opt :pop3_host, "The host POP3 clients will connect to", :default => 'localhost' 15 | opt :pop3_port, "The port POP3 clients will connect to", :default => 1100 16 | opt :once, "Whether to terminate after serving the first client", :default => false 17 | opt :file, "File where messages get written as a single file", :default => "/var/tmp/mailtrap.log" 18 | opt :msg_dir, "Dir where messages are stored temporarily until served via POP3", :default=>"/var/tmp/mailtrap" 19 | end 20 | 21 | options = { 22 | :dir_mode => :normal, 23 | :dir => '/var/tmp', 24 | :multiple => true, 25 | :mode => :exec, 26 | :backtrace => true, 27 | :log_output => true 28 | } 29 | 30 | Daemons.run_proc( 'mailtrap', options ) do 31 | Mailtrap.new( opts[:smtp_host], opts[:smtp_port], opts[:once], opts[:file], opts[:msg_dir]) 32 | end 33 | 34 | Daemons.run_proc( 'mailshovel', options ) do 35 | Mailshovel.connect( opts[:pop3_host], opts[:pop3_port], opts[:msg_dir] ) 36 | end 37 | 38 | -------------------------------------------------------------------------------- /doc/classes/Mailtrap.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Class: Mailtrap 9 | 10 | 11 | 12 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 67 | 68 | 69 | 70 | 71 | 74 | 75 |
ClassMailtrap
In: 58 | 59 | lib/mailtrap.rb 60 | 61 |
62 | 63 | lib/mailtrap/log_parser.rb 64 | 65 |
66 |
Parent: 72 | Object 73 |
76 |
77 | 78 | 79 |
80 | 81 | 82 | 83 |
84 | 85 |
86 |

87 | Class to read a Mailtrap log file and extract 88 | the emails, returning them as TMail objects. (Interim solution until we can 89 | get Mailtrap outputting structured log files.) 90 | — hacked together by Ashley Moran 91 | <ashley.moran@patchspace.co.uk> 06-Apr-2008 92 |

93 | 94 |
95 | 96 | 97 |
98 | 99 |
100 |

Methods

101 | 102 |
103 | accept   104 | new   105 | serve   106 | write   107 |
108 |
109 | 110 |
111 | 112 | 113 | 114 | 115 |
116 | 117 |
118 |

Classes and Modules

119 | 120 | Class Mailtrap::LogParser
121 | 122 |
123 | 124 |
125 |

Constants

126 | 127 |
128 | 129 | 130 | 131 | 132 | 133 | 134 |
VERSION='0.2.1'
135 |
136 |
137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
145 |

Public Class methods

146 | 147 |
148 | 149 | 150 | 156 | 157 |
158 |

159 | Create a new Mailtrap on the specified host:port. If once it 161 | true it will listen for one message then exit. Specify the msgdir where 162 | messages are written. 163 |

164 |
165 |
166 | 167 |

Public Instance methods

168 | 169 |
170 | 171 | 172 | 178 | 179 |
180 |

181 | Service one or more SMTP client connections 182 |

183 |
184 |
185 | 186 |
187 | 188 | 189 | 195 | 196 |
197 |

198 | Talk pidgeon-SMTP to the client to get them to hand over the message and go 199 | away. 200 |

201 |
202 |
203 | 204 |
205 | 206 | 207 | 213 | 214 |
215 |

216 | Write a plain text dump of the incoming email to a text file. The file will 217 | be in the @msgdir folder and will be called smtp0001.msg, smtp0002.msg, and 218 | so on. 219 |

220 |
221 |
222 | 223 | 224 |
225 | 226 | 227 |
228 | 229 | 230 |
231 |

[Validate]

232 |
233 | 234 | 235 | -------------------------------------------------------------------------------- /doc/classes/Mailtrap.src/M000001.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | new (Mailtrap) 9 | 10 | 11 | 12 | 13 |
# File lib/mailtrap.rb, line 19
14 |   def initialize( host, port, once, msgfile )
15 |     @host = host
16 |     @port = port
17 |     @once = once
18 |     @msgfile = msgfile
19 |     
20 |     File.open( @msgfile, "a" ) do |file|
21 |       file.puts "\n* Mailtrap started at #{@host}:#{port}\n"
22 |     end
23 |     
24 |     service = TCPServer.new( @host, @port )
25 |     accept( service )
26 |   end
27 | 28 | -------------------------------------------------------------------------------- /doc/classes/Mailtrap.src/M000002.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | accept (Mailtrap) 9 | 10 | 11 | 12 | 13 |
# File lib/mailtrap.rb, line 34
14 |   def accept( service )
15 |     while session = service.accept
16 |       
17 |       class << session
18 |         def get_line
19 |           line = gets
20 |           line.chomp! unless line.nil?
21 |           line          
22 |         end
23 |       end
24 |       
25 |       begin
26 |         serve( session )
27 |       rescue Exception => e
28 |         puts "Erk! #{e.message}"        
29 |       end
30 |       
31 |       break if @once
32 |     end    
33 |   end
34 | 35 | -------------------------------------------------------------------------------- /doc/classes/Mailtrap.src/M000003.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | write (Mailtrap) 9 | 10 | 11 | 12 | 13 |
# File lib/mailtrap.rb, line 58
14 |   def write( from, to_list, message )
15 |     
16 |     # Strip SMTP commands from To: and From:
17 |     from.gsub!( /MAIL FROM:\s*/, "" )
18 |     to_list = to_list.map { |to| to.gsub( /RCPT TO:\s*/, "" ) }
19 |     
20 |     # Append to the end of the messages file
21 |     File.open( @msgfile, "a" ) do |file|
22 |       file.puts "* Message begins"
23 |       file.puts "From: #{from}"
24 |       file.puts "To: #{to_list.join(", ")}"
25 |       file.puts "Body:"
26 |       file.puts message
27 |       file.puts "\n* Message ends"
28 |     end
29 | 
30 |   end
31 | 32 | -------------------------------------------------------------------------------- /doc/classes/Mailtrap.src/M000004.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | serve (Mailtrap) 9 | 10 | 11 | 12 | 13 |
# File lib/mailtrap.rb, line 78
14 |   def serve( connection )
15 |     connection.puts( "220 #{@host} MailTrap ready ESTMP" )
16 |     helo = connection.get_line # whoever they are
17 |     puts "Helo: #{helo}"
18 |     
19 |     if helo =~ /^EHLO\s+/
20 |       puts "Seen an EHLO"
21 |       connection.puts "250-#{@host} offers just ONE extension my pretty"
22 |       connection.puts "250 HELP"
23 |     end
24 |     
25 |     # Accept MAIL FROM:
26 |     from = connection.get_line
27 |     connection.puts( "250 OK" )
28 |     puts "From: #{from}"
29 |     
30 |     to_list = []
31 |     
32 |     # Accept RCPT TO: until we see DATA
33 |     loop do
34 |       to = connection.get_line
35 |       break if to.nil?
36 | 
37 |       if to =~ /^DATA/
38 |         connection.puts( "354 Start your message" )
39 |         break
40 |       else
41 |         puts "To: #{to}"
42 |         to_list << to
43 |         connection.puts( "250 OK" )
44 |       end
45 |     end
46 |     
47 |     # Capture the message body terminated by <CR>.<CR>
48 |     lines = []
49 |     loop do
50 |       line = connection.get_line
51 |       break if line.nil? || line == "."
52 |       lines << line
53 |       puts "+ #{line}"
54 |     end
55 | 
56 |     # We expect the client will go away now
57 |     connection.puts( "250 OK" )
58 |     connection.gets # Quit
59 |     connection.puts "221 Seeya"
60 |     connection.close
61 |     puts "And we're done with that bozo!"
62 | 
63 |     write( from, to_list, lines.join( "\n" ) )
64 |     
65 |   end
66 | 67 | -------------------------------------------------------------------------------- /doc/classes/Mailtrap/LogParser.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | Class: Mailtrap::LogParser 9 | 10 | 11 | 12 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 70 | 71 |
ClassMailtrap::LogParser
In: 58 | 59 | lib/mailtrap/log_parser.rb 60 | 61 |
62 |
Parent: 68 | Object 69 |
72 |
73 | 74 | 75 |
76 | 77 | 78 | 79 |
80 | 81 | 82 | 83 |
84 | 85 |
86 |

Methods

87 | 88 |
89 | parse   90 |
91 |
92 | 93 |
94 | 95 | 96 | 97 | 98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
109 |

Public Class methods

110 | 111 |
112 | 113 | 114 | 120 | 121 |
122 |

123 | Reads a file by its filename and returns an array of TMail objects 124 |

125 |
126 |
127 | 128 | 129 |
130 | 131 | 132 |
133 | 134 | 135 |
136 |

[Validate]

137 |
138 | 139 | 140 | -------------------------------------------------------------------------------- /doc/classes/Mailtrap/LogParser.src/M000005.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | parse (Mailtrap::LogParser) 9 | 10 | 11 | 12 | 13 |
# File lib/mailtrap/log_parser.rb, line 12
14 |       def parse(filename)
15 |         emails = nil
16 |       
17 |         File.open(filename, "r") do |f|
18 |           emails = extract_messages(f.readlines)
19 |         end
20 |       
21 |         emails.map { |email| TMail::Mail.parse(email) }
22 |       end
23 | 24 | -------------------------------------------------------------------------------- /doc/created.rid: -------------------------------------------------------------------------------- 1 | Sun, 18 Dec 2011 01:40:03 -0500 2 | ./History.txt Sat, 17 Dec 2011 21:56:17 -0500 3 | ./lib/mailtrap/log_parser.rb Sun, 18 Dec 2011 01:27:18 -0500 4 | ./lib/mailshovel.rb Sat, 17 Dec 2011 21:56:17 -0500 5 | ./lib/mailtrap.rb Sat, 17 Dec 2011 21:56:17 -0500 6 | ./Rakefile Sun, 18 Dec 2011 00:27:56 -0500 7 | ./Manifest.txt Sat, 17 Dec 2011 21:56:17 -0500 8 | ./README.txt Sun, 18 Dec 2011 00:24:12 -0500 9 | ./test/mailshovel_test.rb Sat, 17 Dec 2011 21:56:17 -0500 10 | ./test/test_mailtrap.rb Sat, 17 Dec 2011 21:56:17 -0500 11 | ./spec/mailtrap/log_parser_spec.rb Sun, 18 Dec 2011 01:21:26 -0500 12 | ./bin/mailtrap Sat, 17 Dec 2011 21:56:17 -0500 13 | ./Gemfile Sun, 18 Dec 2011 01:26:40 -0500 14 | -------------------------------------------------------------------------------- /doc/files/History_txt.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | File: History.txt 9 | 10 | 11 | 12 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |

History.txt

51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 |
Path:History.txt 55 |
Last Update:Sun Apr 06 18:32:54 +0100 2008
62 |
63 | 64 | 65 |
66 | 67 | 68 | 69 |
70 | 71 |
72 |

1.0.0 / 2007-10-03

73 |
    74 |
  • 1 major enhancement 75 | 76 |
      77 |
    • Birthday! 78 | 79 |
    • 80 |
    81 |
  • 82 |
83 | 84 |
85 | 86 | 87 |
88 | 89 | 90 |
91 | 92 | 93 | 94 | 95 |
96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
108 | 109 | 110 |
111 |

[Validate]

112 |
113 | 114 | 115 | -------------------------------------------------------------------------------- /doc/files/README_txt.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | File: README.txt 9 | 10 | 11 | 12 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |

README.txt

51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 |
Path:README.txt 55 |
Last Update:Sun Apr 06 18:32:54 +0100 2008
62 |
63 | 64 | 65 |
66 | 67 | 68 | 69 |
70 | 71 |
72 |

73 | mailtrap 74 |

75 |
 76 |     by Matt Mower <self@mattmower.com>
 77 |     http://matt.blogs.it/
 78 | 
79 |

DESCRIPTION:

80 |

81 | Mailtrap is a mock SMTP server for 82 | use in Rails development. 83 |

84 |

85 | Mailtrap waits on your choosen port 86 | for a client to connect and talks _just enough_ SMTP protocol for 87 | ActionMailer to successfully deliver its message. 88 |

89 |

90 | Mailtrap makes no attempt to 91 | actually deliver messages and, instead, writes them into a file (hence the 92 | name Mail_trap_). Handy tip: use tail -f to see emails being received. 93 |

94 |

95 | You can configure the hostname (default: localhost) and port (default: 96 | 2525) for the server and also where the messages get written (default: 97 | /var/tmp/mailtrap.log). 98 |

99 |

FEATURES/PROBLEMS:

100 |
    101 |
  • Lightweight, no setup 102 | 103 |
  • 104 |
  • Runs as a daemon with start, stop, etc.. 105 | 106 |
  • 107 |
  • Tested with ActionMailer‘s SMTP delivery method 108 | 109 |
  • 110 |
  • Very, very, dumb … might not work with an arbitrary SMTP client 111 | 112 |
  • 113 |
114 |

SYNOPSIS:

115 |

116 | To use the defaults host:localhost, port:2525, file:/var/log/mailtrap.log 117 |

118 |
    119 |
  • mailtrap start 120 | 121 |
  • 122 |
123 |

124 | Customise startup: 125 |

126 |
    127 |
  • sudo mailtrap start —host my.host —port 25 —once 128 | —file=/var/log/messages.txt 129 | 130 |
  • 131 |
132 |

133 | (sudo because you want to use restricted port 25) 134 |

135 |

136 | For more info: 137 |

138 |
    139 |
  • mailtrap —help (to see Daemonization options) 140 | 141 |
  • 142 |
  • mailtrap start —help (to see Mailtrap options) 144 | 145 |
  • 146 |
147 |

REQUIREMENTS:

148 |
    149 |
  • Hoe rubygem 150 | 151 |
  • 152 |
  • Rubygems rubygem 153 | 154 |
  • 155 |
  • Daemons rubygem 156 | 157 |
  • 158 |
  • Trollop rubygem 159 | 160 |
  • 161 |
162 |

163 | All these are automatically installed if you use gem install -y 164 |

165 |

INSTALL:

166 |
    167 |
  • sudo gem install -y mailtrap 168 | 169 |
  • 170 |
171 |

LICENSE:

172 |

173 | (The MIT License) 174 |

175 |

176 | Copyright (c) 2007 Matt Mower, Software Heretics 177 |

178 |

179 | Permission is hereby granted, free of charge, to any person obtaining a 180 | copy of this software and associated documentation files (the 181 | ‘Software’), to deal in the Software without restriction, 182 | including without limitation the rights to use, copy, modify, merge, 183 | publish, distribute, sublicense, and/or sell copies of the Software, and to 184 | permit persons to whom the Software is furnished to do so, subject to the 185 | following conditions: 186 |

187 |

188 | The above copyright notice and this permission notice shall be included in 189 | all copies or substantial portions of the Software. 190 |

191 |

192 | THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, 193 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 194 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 195 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 196 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 197 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 198 | USE OR OTHER DEALINGS IN THE SOFTWARE. 199 |

200 | 201 |
202 | 203 | 204 |
205 | 206 | 207 |
208 | 209 | 210 | 211 | 212 |
213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 |
225 | 226 | 227 |
228 |

[Validate]

229 |
230 | 231 | 232 | -------------------------------------------------------------------------------- /doc/files/bin/mailtrap.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | File: mailtrap 9 | 10 | 11 | 12 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |

mailtrap

51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 |
Path:bin/mailtrap 55 |
Last Update:Sun Apr 06 18:32:54 +0100 2008
62 |
63 | 64 | 65 |
66 | 67 | 68 | 69 |
70 | 71 |
72 |

73 | Run the mailtrap server 74 |

75 | 76 |
77 | 78 |
79 |

Required files

80 | 81 |
82 | rubygems   83 | daemons   84 | mailtrap   85 |
86 |
87 | 88 |
89 | 90 | 91 |
92 | 93 | 94 | 95 | 96 |
97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 |
109 | 110 | 111 |
112 |

[Validate]

113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /doc/files/lib/mailtrap/log_parser_rb.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | File: log_parser.rb 9 | 10 | 11 | 12 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |

log_parser.rb

51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 |
Path:lib/mailtrap/log_parser.rb 55 |
Last Update:Sun Apr 06 19:06:10 +0100 2008
62 |
63 | 64 | 65 |
66 | 67 | 68 | 69 |
70 | 71 | 72 |
73 |

Required files

74 | 75 |
76 | tmail   77 |
78 |
79 | 80 |
81 | 82 | 83 |
84 | 85 | 86 | 87 | 88 |
89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 | 102 | 103 |
104 |

[Validate]

105 |
106 | 107 | 108 | -------------------------------------------------------------------------------- /doc/files/lib/mailtrap_rb.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | File: mailtrap.rb 9 | 10 | 11 | 12 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |

mailtrap.rb

51 | 52 | 53 | 54 | 56 | 57 | 58 | 59 | 60 | 61 |
Path:lib/mailtrap.rb 55 |
Last Update:Sun Apr 06 19:17:41 +0100 2008
62 |
63 | 64 | 65 |
66 | 67 | 68 | 69 |
70 | 71 | 72 |
73 |

Required files

74 | 75 |
76 | rubygems   77 | daemons   78 | socket   79 | trollop   80 |
81 |
82 | 83 |
84 | 85 | 86 |
87 | 88 | 89 | 90 | 91 |
92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
104 | 105 | 106 |
107 |

[Validate]

108 |
109 | 110 | 111 | -------------------------------------------------------------------------------- /doc/fr_class_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | Classes 15 | 16 | 17 | 18 | 19 | 20 |
21 |

Classes

22 |
23 | Mailtrap
24 | Mailtrap::LogParser
25 |
26 |
27 | 28 | -------------------------------------------------------------------------------- /doc/fr_file_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | Files 15 | 16 | 17 | 18 | 19 | 20 |
21 |

Files

22 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /doc/fr_method_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | Methods 15 | 16 | 17 | 18 | 19 | 20 | 30 | 31 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | RDoc Documentation 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 87 | 88 |

This is the API documentation for RDoc Documentation. 89 | 90 | 91 |

96 | 97 | -------------------------------------------------------------------------------- /doc/rdoc-style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font-family: Verdana,Arial,Helvetica,sans-serif; 4 | font-size: 90%; 5 | margin: 0; 6 | margin-left: 40px; 7 | padding: 0; 8 | background: white; 9 | } 10 | 11 | h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } 12 | h1 { font-size: 150%; } 13 | h2,h3,h4 { margin-top: 1em; } 14 | 15 | a { background: #eef; color: #039; text-decoration: none; } 16 | a:hover { background: #039; color: #eef; } 17 | 18 | /* Override the base stylesheet's Anchor inside a table cell */ 19 | td > a { 20 | background: transparent; 21 | color: #039; 22 | text-decoration: none; 23 | } 24 | 25 | /* and inside a section title */ 26 | .section-title > a { 27 | background: transparent; 28 | color: #eee; 29 | text-decoration: none; 30 | } 31 | 32 | /* === Structural elements =================================== */ 33 | 34 | div#index { 35 | margin: 0; 36 | margin-left: -40px; 37 | padding: 0; 38 | font-size: 90%; 39 | } 40 | 41 | 42 | div#index a { 43 | margin-left: 0.7em; 44 | } 45 | 46 | div#index .section-bar { 47 | margin-left: 0px; 48 | padding-left: 0.7em; 49 | background: #ccc; 50 | font-size: small; 51 | } 52 | 53 | 54 | div#classHeader, div#fileHeader { 55 | width: auto; 56 | color: white; 57 | padding: 0.5em 1.5em 0.5em 1.5em; 58 | margin: 0; 59 | margin-left: -40px; 60 | border-bottom: 3px solid #006; 61 | } 62 | 63 | div#classHeader a, div#fileHeader a { 64 | background: inherit; 65 | color: white; 66 | } 67 | 68 | div#classHeader td, div#fileHeader td { 69 | background: inherit; 70 | color: white; 71 | } 72 | 73 | 74 | div#fileHeader { 75 | background: #057; 76 | } 77 | 78 | div#classHeader { 79 | background: #048; 80 | } 81 | 82 | 83 | .class-name-in-header { 84 | font-size: 180%; 85 | font-weight: bold; 86 | } 87 | 88 | 89 | div#bodyContent { 90 | padding: 0 1.5em 0 1.5em; 91 | } 92 | 93 | div#description { 94 | padding: 0.5em 1.5em; 95 | background: #efefef; 96 | border: 1px dotted #999; 97 | } 98 | 99 | div#description h1,h2,h3,h4,h5,h6 { 100 | color: #125;; 101 | background: transparent; 102 | } 103 | 104 | div#validator-badges { 105 | text-align: center; 106 | } 107 | div#validator-badges img { border: 0; } 108 | 109 | div#copyright { 110 | color: #333; 111 | background: #efefef; 112 | font: 0.75em sans-serif; 113 | margin-top: 5em; 114 | margin-bottom: 0; 115 | padding: 0.5em 2em; 116 | } 117 | 118 | 119 | /* === Classes =================================== */ 120 | 121 | table.header-table { 122 | color: white; 123 | font-size: small; 124 | } 125 | 126 | .type-note { 127 | font-size: small; 128 | color: #DEDEDE; 129 | } 130 | 131 | .xxsection-bar { 132 | background: #eee; 133 | color: #333; 134 | padding: 3px; 135 | } 136 | 137 | .section-bar { 138 | color: #333; 139 | border-bottom: 1px solid #999; 140 | margin-left: -20px; 141 | } 142 | 143 | 144 | .section-title { 145 | background: #79a; 146 | color: #eee; 147 | padding: 3px; 148 | margin-top: 2em; 149 | margin-left: -30px; 150 | border: 1px solid #999; 151 | } 152 | 153 | .top-aligned-row { vertical-align: top } 154 | .bottom-aligned-row { vertical-align: bottom } 155 | 156 | /* --- Context section classes ----------------------- */ 157 | 158 | .context-row { } 159 | .context-item-name { font-family: monospace; font-weight: bold; color: black; } 160 | .context-item-value { font-size: small; color: #448; } 161 | .context-item-desc { color: #333; padding-left: 2em; } 162 | 163 | /* --- Method classes -------------------------- */ 164 | .method-detail { 165 | background: #efefef; 166 | padding: 0; 167 | margin-top: 0.5em; 168 | margin-bottom: 1em; 169 | border: 1px dotted #ccc; 170 | } 171 | .method-heading { 172 | color: black; 173 | background: #ccc; 174 | border-bottom: 1px solid #666; 175 | padding: 0.2em 0.5em 0 0.5em; 176 | } 177 | .method-signature { color: black; background: inherit; } 178 | .method-name { font-weight: bold; } 179 | .method-args { font-style: italic; } 180 | .method-description { padding: 0 0.5em 0 0.5em; } 181 | 182 | /* --- Source code sections -------------------- */ 183 | 184 | a.source-toggle { font-size: 90%; } 185 | div.method-source-code { 186 | background: #262626; 187 | color: #ffdead; 188 | margin: 1em; 189 | padding: 0.5em; 190 | border: 1px dashed #999; 191 | overflow: hidden; 192 | } 193 | 194 | div.method-source-code pre { color: #ffdead; overflow: hidden; } 195 | 196 | /* --- Ruby keyword styles --------------------- */ 197 | 198 | .standalone-code { background: #221111; color: #ffdead; overflow: hidden; } 199 | 200 | .ruby-constant { color: #7fffd4; background: transparent; } 201 | .ruby-keyword { color: #00ffff; background: transparent; } 202 | .ruby-ivar { color: #eedd82; background: transparent; } 203 | .ruby-operator { color: #00ffee; background: transparent; } 204 | .ruby-identifier { color: #ffdead; background: transparent; } 205 | .ruby-node { color: #ffa07a; background: transparent; } 206 | .ruby-comment { color: #b22222; font-weight: bold; background: transparent; } 207 | .ruby-regexp { color: #ffa07a; background: transparent; } 208 | .ruby-value { color: #7fffd4; background: transparent; } -------------------------------------------------------------------------------- /lib/mailshovel.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'daemons' 3 | require 'socket' 4 | require 'trollop' 5 | 6 | # 7 | # Speaks pidgin-POP3 to serve out mail in mailtrap's folder format. 8 | # If nothing works, look at the \r\n conversion: this was written and tested on a mac. 9 | # 10 | class Mailshovel 11 | VERSION = '0.1' 12 | 13 | def initialize(service, msgdir) 14 | @service = service 15 | @msgdir = msgdir 16 | @deletion_queue = [] 17 | 18 | accept(service) 19 | end 20 | 21 | def self.connect(host, port, msgdir) 22 | service = TCPServer.new(host, port) 23 | new(service, msgdir) 24 | end 25 | 26 | # Service one or more POP3 client connections 27 | def accept( service ) 28 | while session = service.accept 29 | class << session 30 | def get_line 31 | line = gets 32 | line.chomp! unless line.nil? 33 | line 34 | end 35 | end 36 | begin 37 | serve( session ) 38 | rescue Exception => e 39 | warn e.message 40 | puts "Erk! #{e.message}" 41 | end 42 | end 43 | end 44 | 45 | #Return an array of full filenames, in the proper order 46 | def messages 47 | Dir.entries(@msgdir).collect{|e| File.join(@msgdir,e) if e[0..0] != "."}.compact.sort 48 | end 49 | 50 | # Talk pidgin-POP3 to the client 51 | def serve( connection ) 52 | puts "Connected." 53 | senddata connection, "+OK Mailshovel. I'm a testing server that speaks poor POP3: try Apple's Mail.App if nothing else works.\r\n" 54 | loop do 55 | if incoming = connection.get_line 56 | puts "< #{incoming}" 57 | case incoming.split(" ")[0] 58 | when "USER", "PASS" 59 | senddata connection, "+OK Whatever, man.\r\n" 60 | when "STAT" 61 | senddata connection, "+OK #{messages.length} #{messages.length}\r\n" 62 | when "UIDL" 63 | senddata connection, "+OK\r\n" 64 | 0.upto(messages.length-1) do |n| #Fudging needed since POP3 indices start at 1, not 0 65 | senddata connection, "#{n+1} #{messages[n].gsub("/","Z")}\r\n" 66 | end 67 | senddata connection, ".\r\n" 68 | when "LIST" 69 | senddata connection, "+OK\r\n" 70 | 0.upto(messages.length-1) do |n| 71 | senddata connection, "#{n+1} 100\r\n" 72 | end 73 | senddata connection, ".\r\n" 74 | when "RETR" 75 | senddata connection, "+OK\r\n" 76 | senddata connection, File.new(messages[incoming.split(" ")[1].to_i-1],"r").read.gsub("\r","\r\n") 77 | senddata connection, "\r\n.\r\n" 78 | when "DELE" 79 | senddata connection, "+OK\r\n" 80 | schedule_for_delete(messages[incoming.split(" ")[1].to_i-1]) 81 | when "QUIT" 82 | senddata connection, "+OK Closing connection.\r\n" 83 | delete_pending_mail 84 | connection.close 85 | else 86 | senddata connection, "-ERR I don't understand '#{incoming}'.\r\n" 87 | end 88 | end 89 | end 90 | end 91 | 92 | def senddata(connection,data) 93 | puts "> #{data}" 94 | connection.puts data 95 | end 96 | 97 | private 98 | 99 | def schedule_for_delete(message) 100 | @deletion_queue << message 101 | end 102 | 103 | def delete_pending_mail 104 | @deletion_queue.each { |message| FileUtils.rm(message) } 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /lib/mailtrap.rb: -------------------------------------------------------------------------------- 1 | require 'base64' 2 | require 'rubygems' 3 | require 'daemons' 4 | require 'socket' 5 | require 'trollop' 6 | 7 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__))) 8 | 9 | # 10 | # Mailtrap creates a TCP server that listens on a specified port for SMTP 11 | # clients. Accepts the connection and talks just enough of the SMTP protocol 12 | # for them to deliver a message which it writes to disk. 13 | # 14 | # Based on Matt Mower's original; slightly modified by Gwyn Morfey to write messages 15 | # as separate files so that we can serve them out by POP3. 16 | class Mailtrap 17 | VERSION = '0.2.3.20130709144258' 18 | 19 | # Create a new Mailtrap on the specified host:port. If once it true it 20 | # will listen for one message then exit. Specify the msgdir where messages 21 | # are written. 22 | def initialize( host, port, once, msgfile, msgdir ) 23 | @host = host 24 | @port = port 25 | @once = once 26 | @msgfile = msgfile 27 | @msgdir = msgdir 28 | @msgnum = 0 29 | 30 | File.open( @msgfile, "a" ) do |file| 31 | file.puts "\n* Mailtrap started at #{@host}:#{port}\n" 32 | end 33 | 34 | service = TCPServer.new( @host, @port ) 35 | accept( service ) 36 | end 37 | 38 | # Service one or more SMTP client connections 39 | def accept( service ) 40 | while session = service.accept 41 | 42 | class << session 43 | def get_line 44 | line = gets 45 | line.chomp! unless line.nil? 46 | line 47 | end 48 | end 49 | 50 | begin 51 | serve( session ) 52 | rescue Exception => e 53 | puts "Erk! #{e.message}" 54 | end 55 | 56 | break if @once 57 | end 58 | end 59 | 60 | # Write a plain text dump of the incoming email to a text 61 | # file. The file will be in the @msgdir folder and will 62 | # be called smtp0001.msg, smtp0002.msg, and so on. 63 | def write( from, to_list, message ) 64 | @msgnum += 1 65 | 66 | # Strip SMTP commands from To: and From: 67 | from.gsub!( /MAIL FROM:\s*/, "" ) 68 | to_list = to_list.map { |to| to.gsub( /RCPT TO:\s*/, "" ) } 69 | 70 | # Append to the end of the messages file 71 | File.open( @msgfile, "a" ) do |file| 72 | file.puts "* Message begins" 73 | file.puts "From: #{from}" 74 | file.puts "To: #{to_list.join(", ")}" 75 | file.puts "Body:" 76 | file.puts message 77 | file.puts "\n* Message ends" 78 | end 79 | 80 | #Write into msgs dir, also - Mailshovel needs this. 81 | Dir.mkdir(@msgdir) if !File.exist?(@msgdir) 82 | File.open(File.join(@msgdir,Time.now.to_i.to_s + "_" + sprintf("%03.0f",@msgnum) + ".txt"),"w") do |file| 83 | file.puts message 84 | end 85 | end 86 | 87 | # Talk pidgeon-SMTP to the client to get them to hand over the message 88 | # and go away. 89 | def serve( connection ) 90 | connection.puts( "220 #{@host} MailTrap ready ESMTP" ) 91 | 92 | # Keep handling commands until we see a MAIL FROM: 93 | from = nil 94 | loop do 95 | client_cmd = connection.get_line 96 | if client_cmd =~ /^EHLO\s*/ 97 | puts "Seen an EHLO" 98 | connection.puts "250-#{@host} offers just ONE extension my pretty" 99 | connection.puts "250 HELP" 100 | elsif client_cmd =~ /^HELO\s*/ 101 | puts "Helo: #{client_cmd}" 102 | connection.puts '250 OK' 103 | elsif client_cmd =~ /^STARTTLS$/ 104 | #connection.puts "220 Ready to start TLS" ## TODO: if ye want a challenge 105 | connection.puts "454 TLS not available due to temporary reason (mailtrap doesn't support it yet)" 106 | connection.close 107 | elsif client_cmd =~ /^AUTH LOGIN$/ 108 | # Support plaintext login 109 | connection.puts "334 VXNlcm5hbWU6" # 334 Username: 110 | username = Base64.decode64(connection.get_line) 111 | connection.puts "334 UGFzc3dvcmQ6" # 334 Password: 112 | password = Base64.decode64(connection.get_line) 113 | connection.puts '235 Authentication succeeded' 114 | log_credentials(username, password) 115 | elsif client_cmd =~ /^AUTH LOGIN\s+.+$/ 116 | # Support alternate login style 117 | username = Base64.decode64(client_cmd.gsub(/^AUTH LOGIN\s+(.+)$/, '\1')) 118 | connection.puts "334 UGFzc3dvcmQ6" # 334 Password: 119 | password = Base64.decode64(connection.get_line) 120 | connection.puts '235 Authentication succeeded' 121 | log_credentials(username, password) 122 | elsif client_cmd =~ /^NOOP$/ 123 | connection.puts '250 OK' 124 | elsif client_cmd =~ /^MAIL FROM:.*/i 125 | # Accept MAIL FROM: 126 | from = client_cmd 127 | puts "From: #{from}" 128 | connection.puts '250 OK' 129 | elsif client_cmd =~ /^QUIT$/ 130 | connection.puts '221 Seeya' 131 | connection.close 132 | else 133 | # Not sure what they said... Eat the command & look the other way? 134 | puts "Fishy client sent us: #{client_cmd}" 135 | connection.puts '250 OK' 136 | end 137 | 138 | break if !from.nil? 139 | end 140 | 141 | 142 | to_list = [] 143 | 144 | # Accept RCPT TO: until we see DATA 145 | loop do 146 | to = connection.get_line 147 | break if to.nil? 148 | 149 | if to =~ /^DATA/ 150 | connection.puts( "354 Start your message" ) 151 | break 152 | else 153 | puts "To: #{to}" 154 | to_list << to 155 | connection.puts( "250 OK" ) 156 | end 157 | end 158 | 159 | # Capture the message body terminated by . 160 | lines = [] 161 | loop do 162 | line = connection.get_line 163 | break if line.nil? || line == "." 164 | lines << line 165 | puts "+ #{line}" 166 | end 167 | 168 | # We expect the client will go away now 169 | connection.puts( "250 OK" ) 170 | connection.gets # Quit 171 | connection.puts "221 Seeya" 172 | connection.close 173 | puts "And we're done with that bozo!" 174 | 175 | write( from, to_list, lines.join( "\n" ) ) 176 | 177 | end 178 | 179 | private 180 | 181 | def log_credentials(username, password) 182 | puts "What a silly client, it sent us their password in the clear!" if !username.nil? && !password.nil? && !password.empty? 183 | puts "Username: #{username}" 184 | puts "Password: #{password}" 185 | end 186 | 187 | end 188 | -------------------------------------------------------------------------------- /lib/mailtrap/log_parser.rb: -------------------------------------------------------------------------------- 1 | require 'mail' 2 | 3 | # Class to read a Mailtrap log file and extract the emails, 4 | # returning them as Mail objects. (Interim solution until 5 | # we can get Mailtrap outputting structured log files.) 6 | # -- 7 | # hacked together by Ashley Moran 06-Apr-2008 8 | class Mailtrap 9 | class LogParser 10 | class << self 11 | # Reads a file by its filename and returns an array of Mail objects 12 | def parse(filename) 13 | emails = nil 14 | 15 | File.open(filename, "r") do |f| 16 | emails = extract_messages(f.readlines) 17 | end 18 | 19 | emails.map { |email| Mail.new(email) } 20 | end 21 | 22 | private 23 | 24 | def extract_messages(lines) 25 | in_message = false 26 | 27 | messages = [] 28 | message_lines = [] 29 | 30 | lines.each do |line| 31 | next if line =~ /^\* Mailtrap started/ 32 | 33 | if line.chomp == "* Message begins" 34 | in_message = true 35 | next 36 | end 37 | 38 | if in_message && line !~ /^\* Message (begins|ends)/ 39 | message_lines << line 40 | end 41 | 42 | if line.chomp == "* Message ends" 43 | messages << message_lines.join 44 | message_lines = [] 45 | in_message = false 46 | next 47 | end 48 | end 49 | 50 | messages 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /mailtrap.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "mailtrap" 5 | s.version = "0.2.3.20130709144258" 6 | 7 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 8 | s.authors = ["Matt Mower"] 9 | s.date = "2013-07-09" 10 | s.description = "Mailtrap is a mock SMTP server for use in Rails development. This package also includes Mailshovel, a mock POP3 server that works with Mailtrap. You can configure your mail client (eg Mail.App) to connect to Mailshovel, and then manage messages that ActionMailer has \"sent\" using its GUI.\n\nMailtrap waits on your chosen port for a client to connect and talks _just enough_ SMTP protocol for ActionMailer to successfully deliver its message.\n\nMailtrap makes *no* attempt to actually deliver messages and, instead, writes them into a series of files which are read by Mailshovel.\n\nYou can configure the hostname (default: localhost) and port (default: 2525) for the server and also where the messages get written (default: /var/tmp/mailtrap.log)." 11 | s.email = ["self@mattmower.com"] 12 | s.executables = ["mailtrap"] 13 | s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"] 14 | s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "bin/mailtrap", "lib/mailtrap.rb", "lib/mailshovel.rb", "test/test_mailtrap.rb", "test/mailshovel_test.rb", ".gemtest"] 15 | s.homepage = "http://matt.blogs.it/" 16 | s.rdoc_options = ["--main", "README.txt"] 17 | s.require_paths = ["lib"] 18 | s.rubyforge_project = "simplyruby" 19 | s.rubygems_version = "1.8.25" 20 | s.summary = "Mailtrap is a mock SMTP server for use in Rails development" 21 | s.test_files = ["test/test_mailtrap.rb", "test/mailshovel_test.rb"] 22 | 23 | if s.respond_to? :specification_version then 24 | s.specification_version = 3 25 | 26 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 27 | s.add_runtime_dependency(%q, [">= 1.0.8"]) 28 | s.add_runtime_dependency(%q, [">= 1.7"]) 29 | s.add_runtime_dependency(%q, ["~> 2.3.0"]) 30 | s.add_development_dependency(%q, ["~> 4.0"]) 31 | s.add_development_dependency(%q, ["~> 3.6"]) 32 | else 33 | s.add_dependency(%q, [">= 1.0.8"]) 34 | s.add_dependency(%q, [">= 1.7"]) 35 | s.add_dependency(%q, ["~> 2.3.0"]) 36 | s.add_dependency(%q, ["~> 4.0"]) 37 | s.add_dependency(%q, ["~> 3.6"]) 38 | end 39 | else 40 | s.add_dependency(%q, [">= 1.0.8"]) 41 | s.add_dependency(%q, [">= 1.7"]) 42 | s.add_dependency(%q, ["~> 2.3.0"]) 43 | s.add_dependency(%q, ["~> 4.0"]) 44 | s.add_dependency(%q, ["~> 3.6"]) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/mailtrap/log_parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rspec' 3 | 4 | require File.join(File.dirname(__FILE__), %w[ .. .. lib mailtrap log_parser ]) 5 | 6 | LOG_DIR = File.join(File.dirname(__FILE__), 'sample_logs') 7 | SAMPLE_LOG_FILENAME = File.join(LOG_DIR, 'sample.log') 8 | SAMPLE_EMPTY_LOG_FILENAME = File.join(LOG_DIR, 'sample_empty.log') 9 | 10 | describe Mailtrap::LogParser do 11 | it "should extract two emails from the sample log file" do 12 | emails = Mailtrap::LogParser.parse(SAMPLE_LOG_FILENAME) 13 | expect(emails.size).to eq(2) 14 | end 15 | 16 | it "should extract the details of each email" do 17 | emails = Mailtrap::LogParser.parse(SAMPLE_LOG_FILENAME) 18 | 19 | # let's just enough of TMail to know it's doing something useful... 20 | expect(emails[0].destinations).to eq %w[ recipient@test.com ] 21 | expect(emails[1].destinations).to eq %w[ bear@zoo.com giraffe@zoo.com ] 22 | 23 | expect(emails[0].body).to include("Body content A") 24 | expect(emails[1].body).to include("Body content B") 25 | end 26 | 27 | it "should not include the message boundary lines" do 28 | emails = Mailtrap::LogParser.parse(SAMPLE_LOG_FILENAME) 29 | 30 | expect(emails[0].body).to_not include("* Message begins") 31 | expect(emails[0].body).to_not include("* Message ends") 32 | 33 | expect(emails[1].body).to_not include("* Message begins") 34 | expect(emails[1].body).to_not include("* Message ends") 35 | end 36 | 37 | it "should handle an empty file" do 38 | emails = Mailtrap::LogParser.parse(SAMPLE_EMPTY_LOG_FILENAME) 39 | expect(emails).to be_empty 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/mailtrap/sample_logs/sample.log: -------------------------------------------------------------------------------- 1 | * Mailtrap started at localhost:2525 2 | * Message begins 3 | From: 4 | To: 5 | Body: 6 | Date: Sat, 5 Apr 2008 14:59:55 +0100 7 | Message-Id: <47f785db61179_7a2124e658141@basilisk.home.tmail> 8 | Mime-Version: 1.0 9 | Content-Type: text/plain; charset=utf-8 10 | 11 | Body content A 12 | 13 | * Message ends 14 | * Message begins 15 | From: "Zoo Keeper" 16 | To: "Bear" , "Giraffe" 17 | Body: 18 | Date: Sat, 5 Apr 2008 15:10:51 +0100 19 | Message-Id: <47f7886bdaec4_7a212af958226@basilisk.home.tmail> 20 | Mime-Version: 1.0 21 | Content-Type: text/plain; charset=utf-8 22 | 23 | Body content B 24 | 25 | * Message ends -------------------------------------------------------------------------------- /spec/mailtrap/sample_logs/sample_empty.log: -------------------------------------------------------------------------------- 1 | * Mailtrap started at localhost:2525 -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --colour 2 | --format progress 3 | -------------------------------------------------------------------------------- /test/mailshovel_test.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'shoulda' 3 | require 'mocha/setup' 4 | require 'fileutils' 5 | require File.join(File.dirname(__FILE__), *%w[.. lib mailshovel]) 6 | 7 | require 'fakefs' 8 | 9 | class FakeTCP 10 | attr_reader :input, :output 11 | 12 | def initialize 13 | @input, @output = StringIO.new, StringIO.new 14 | end 15 | 16 | def accept 17 | self 18 | end 19 | 20 | def puts(string) 21 | @output.puts(string) 22 | end 23 | 24 | def say(string) 25 | @input.puts(string + "\r\n") 26 | @input.rewind 27 | sleep(0.1) 28 | end 29 | 30 | def gets 31 | @input.gets 32 | end 33 | 34 | def close 35 | true 36 | end 37 | end 38 | 39 | class MailshovelTest < Test::Unit::TestCase 40 | 41 | context "Mailshovel" do 42 | setup do 43 | FileUtils.mkdir_p("/tmp/mailshovel") 44 | $stdout = StringIO.new 45 | @connection = FakeTCP.new 46 | end 47 | 48 | teardown do 49 | FakeFS::FileSystem.clear 50 | $stdout = STDOUT 51 | @runner.kill if @runner 52 | end 53 | 54 | should "say hello to the connection" do 55 | @runner = Thread.new do 56 | @mailshovel = Mailshovel.new(@connection, '/tmp/mailshovel') 57 | end 58 | 59 | with_mailshovel { } 60 | 61 | assert_received_response(/\+OK Mailshovel/) 62 | end 63 | 64 | should "respond to LIST command with a list of mail" do 65 | 3.times { |x| FileUtils.touch("/tmp/mailshovel/message#{x + 1}.txt") } 66 | 67 | with_mailshovel do 68 | say("LIST") 69 | end 70 | 71 | assert_received_response(/1 100\r\n2 100\r\n3 100\r\n/) 72 | end 73 | 74 | should "respond to STAT command with the number of messages" do 75 | 3.times { |x| FileUtils.touch("/tmp/mailshovel/message#{x + 1}.txt") } 76 | 77 | with_mailshovel do 78 | say("STAT") 79 | end 80 | 81 | assert_received_response(/OK 3 3/) 82 | end 83 | 84 | context "when sending a DELE command" do 85 | setup do 86 | 2.times do |x| 87 | File.open("/tmp/mailshovel/message#{x + 1}.txt", 'w+') do |io| 88 | io.write("MESSAGE #{x + 1}") 89 | end 90 | end 91 | end 92 | 93 | teardown do 94 | FakeFS::FileSystem.clear 95 | end 96 | 97 | should "not immediately delete messages" do 98 | with_mailshovel do 99 | say("DELE 2") 100 | end 101 | 102 | assert File.exist?("/tmp/mailshovel/message2.txt") 103 | end 104 | 105 | should "still allow the message to be retrieved using its original index" do 106 | with_mailshovel do 107 | say("DELE 1") 108 | say("RETR 1") 109 | end 110 | 111 | assert_received_response(/MESSAGE 1/) 112 | end 113 | 114 | should "delete messages on QUIT" do 115 | with_mailshovel do 116 | say("DELE 1") 117 | say("DELE 2") 118 | say("QUIT") 119 | end 120 | 121 | assert !File.exist?("/tmp/mailshovel/message1.txt") 122 | assert !File.exist?("/tmp/mailshovel/message2.txt") 123 | end 124 | end 125 | end 126 | 127 | private 128 | 129 | def with_mailshovel(&block) 130 | @runner = Thread.new do 131 | @mailshovel = Mailshovel.new(@connection, '/tmp/mailshovel') 132 | end 133 | 134 | @connection.instance_eval(&block) 135 | sleep 1 136 | end 137 | 138 | def assert_received_response(regexp) 139 | @connection.output.rewind 140 | assert_match regexp, @connection.output.read 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /test/test_mailtrap.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmower/mailtrap/67d78d443f1ac6656e6369b95f544c2470d191f6/test/test_mailtrap.rb --------------------------------------------------------------------------------