class RubyQmail::Queue
Constants
- QMAIL_ERRORS
- QMAIL_QUEUE_SUCCESS
Attributes
Public Class Methods
Class Method to place the message into the Qmail queue.
# File lib/queue.rb, line 33 def self.insert(return_path, recipients, message, *options) q = Queue.new(return_path, recipients, message, *options) if q.options.has_key?[:ip] || q.options[:method]==:qmqp q.qmqp else q.qmail_queue end end
Recipients can be a filename, array or other object that responds to :each, or to_s resolves to an email address Message can be a filename, string, or other object that responds to :each
# File lib/queue.rb, line 44 def initialize(return_path=nil, recipients=nil, message=nil, *options) parameters(return_path, recipients, message, options) end
Public Instance Methods
Sends email directly via qmail-remote. It does not store in the queue, It will halt the process and wait for the network event to complete. If multiple recipients are passed, it will run qmail-remote delivery for each at a time to honor VERP return paths.
# File lib/queue.rb, line 139 def qmail_remote(return_path=nil, recipients=nil, message=nil, *options) parameters(return_path, recipients, message, options) rp1, rp2 = @return_path.split(/@/,2) rp = @return_path @recipients.each do |recip| unless @options[:noverp] mailbox, host = recip.split(/@/) rp = "#{rp1}#{mailbox}=#{host}@#{rp2}" end @message.rewind if @message.respond_to?(:rewind) cmd = "#{@options[:qmail_root]}+/bin/qmail-remote #{host} #{rp} #{recip}" @success = self.spawn_command(cmd) do |send, recv| @message.each { |m| send.puts m } send.close @response = recv.readpartial(1000) end @options[:logger].info("RubyQmail Remote #{recip} exited:#{@success} responded:#{@response}") end return [ @success, @response ] # Last one end
Builds the QMQP request, and opens a connection to the QMQP Server and sends This implemtents the QMQP protocol, so does not need Qmail installed on the host system. System defaults will be used if no ip or port given. Returns true on success, false on failure (see @response), or nul on deferral
# File lib/queue.rb, line 92 def qmqp(return_path=nil, recipients=nil, message=nil, *options) parameters(return_path, recipients, message, options) begin ip = @options[:ip] || File.readlines(QMQP_SERVERS).first.chomp #puts "CONNECT #{:ip}, #{@options[:qmqp_port]}" socket = TCPSocket.new(ip, @options[:qmqp_port]) raise "QMQP can not connect to #{ip}:#{@options[:qmqp_port]}" unless socket # Build netstring of messagebody+returnpath+recipient... nstr = (@message.map.join("\n")+"\n").to_netstring # { |m| m }.join("\t").to_netstring nstr += @return_path.to_netstring nstr += @recipients.map { |r| r.to_netstring }.join socket.send( nstr.to_netstring, 0 ) @response = socket.recv(1000) # "23:Kok 1182362995 qp 21894," (its a netstring) @success = case @response.match(/^\d+:([KZD])(.+),$/)[1] when 'K' then true # success when 'Z' then nil # deferral when 'D' then false # failure else false end logmsg = "RubyQmail QMQP [#{ip}:#{@options[:qmqp_port]}]: #{@response} return:#{@success}" @options[:logger].info(logmsg) puts logmsg @success rescue Exception => e @options[:logger].error( "QMQP can not connect to #{@opt[:qmqp_ip]}:#{@options[:qmqp_port]} #{e}" ) raise e ensure socket.close if socket end end
This calls the Qmail-Queue program, so requires qmail to be installed (does not require it to be currently running).
# File lib/queue.rb, line 67 def queue(return_path=nil, recipients=nil, message=nil, *options) parameters(return_path, recipients, message, options) @success = run_qmail_queue() do |msg, env| # Send the Message @message.each { |m| msg.puts(m) } msg.close env.write('F' + @return_path + "\0") @recipients.each { |r| env.write('T' + r + "\0") } env.write("\0") # End of "file" end @options[:logger].info("RubyQmail Queue exited:#{@success} #{Queue.qmail_queue_error_message(@success)}") return true if @success == QMAIL_QUEUE_SUCCESS raise Queue.qmail_queue_error_message(@success) end
Forks, sets up stdin and stdout pipes, and starts qmail-queue. IF a block is passed, yields to it with [sendpipe, receivepipe], and returns the exist cod, otherwise returns {:msg=>pipe, :env=>pipe, :pid=>@child} It exits 0 on success or another code on failure. Qmail-queue Protocol: Reads mail message from File Descriptor 0, then reads Envelope from FD 1 Envelope Stream: 'F' + sender_email + “0” + (“T” + recipient_email + “0”) … + “0”
# File lib/queue.rb, line 206 def run_qmail_queue(command=nil, &block) # Set up pipes and qmail-queue child process msg_read, msg_write = IO.pipe env_read, env_write = IO.pipe @child=fork # child? nil : childs_process_id unless @child ## Set child's stdin(0) to read from msg $stdin.close # FD=0 msg_read.dup msg_read.close msg_write.close ## Set child's stdout(1) to read from env $stdout.close # FD=1 env_read.dup env_read.close env_write.close # Change directory and load command Dir.chdir(@options[:qmail_root]) exec( command || @options[:qmail_queue] ) raise "Exec qmail-queue failed" end # Parent Process with block if block_given? yield(msg_write, env_write) # msg_write.close env_write.close wait(@child) @success = $? >> 8 # puts "#{$$} parent waited for #{@child} s=#{@success} #{$?.inspect}" return @sucess end # Parent process, no block {:msg=>msg_write, :env=>env_write, :pid=>@child} end
Forks, sets up stdin and stdout pipes, and starts the command. IF a block is passed, yeilds to it with [sendpipe, receivepipe], returing the exit code, otherwise returns {:send=>, :recieve=>, :pid=>} qmail-queue does not work with this as it reads from both pipes.
# File lib/queue.rb, line 166 def spawn_command(command, &block) child_read, parent_write = IO.pipe # From parent to child(stdin) parent_read, child_write = IO.pipe # From child(stdout) to parent @child = fork # Child process unless @child # $stdin.close # closes FD==0 child_read.dup # copies to FD==0 child_read.close $stdout.close # closes FD==1 child_write.dup # copies to FD==1 child_write.close Dir.chdir(@options[:qmail_root]) unless @options[:nochdir] exec(command) raise "Exec spawn_command #{command} failed" end # Parent Process with block if block_given? yield(parent_write, parent_read) parent_write.close parent_read.close wait(@child) @success = $? >> 8 return @sucess end # Parent process, no block {:send=>parent_write, :receive=>parent_read, :pid=>@child} end