ChrisMar035 Adventures in Web Programming

15Feb/110

Ruby Auto-Responder for Postfix/Vmail

Not knowing perl, I set out to write my own script in Ruby to send auto-responses from e-mail addresses setup in a vmail folder structure. You don't need to use postfix, just have the 'new' folder where new messages are stored.

I recently moved our company's e-mail server to a new VPS on slicehost and started using postfix. I followed this great tutorial on HowToForge (except for the squirrelMail part) to setup a mysql database with the vmail folders to store the mail. I couldn't get the Autoresponder in the tutorial to function correctly. Actually, it was 'eating' random mail, which was a weeks worth of headache in itself. So, we rolled out without any auto-response capabilities.

I've been half-heartedly looking for another solution every few weeks, but haven't found anything other than a few Perl scripts (I'm not really famaliar with perl). So, yesterday I decided to write a ruby script to send auto-responses. I wanted to use text files to hold each autoresponse. Then, I'd just move them int and out of a folder to activate and deactivate them. I also took advantage of the 'new' folder within the vmail structure to reply to mail newer than the autoresponder config file.

The format of the config files is:

email_address_to_respond_from

subject of response

message of response

The message can be multiple lines until the end of the file.

For each config file in the directory, the script parses the file for the above information. Then it will check that user's new mail folder for files. Any new mail file which was modified (sent) after the modification date of the config file is parsed to get the sender's address(es)*. A new e-mail is composed using benprew's pony gem and sent to each of the senders. Finally, the script 'touches' the config file to move the modification date newer than the messages for which it just responded.

The nifty parts of the script are iterating over a directory listing of files:

Dir.new(directory_path).each { |filename| puts filename }

and using the stat method of a file object to get the modified dateTime:

file.stat.mtime

and lastly, using the FileUtils module to 'touch' a file:

FileUtils.touch "#{file_path}/#{file_name}"

Here's the full source:

require 'fileutils'
require 'rubygems'
require 'pony'


# Set absolute path for config file directory
conf_path = "/home/auto-responses"

#for each config file (autoresponder)
Dir.new(conf_path).each do |response_file|
  # don't act on this file or linux '.' listings
  unless response_file[0,1] == "." || response_file == "auto-response-script.rb"
    #create a file for reading
    file = File.new("#{conf_path}/#{response_file}")
    
    # get the last modified time of the current config file
    last_date = file.stat.mtime

    # e-mail address to respond from is first line
    address = file.gets.chomp
   
    #skip blank lines
    begin
      line = file.gets
    end while line == "\n"

    # subject is next
    base_subject = "#{line.chomp} (Auto-Response)"

    #skip blank lines
    begin
      line = file.gets
    end while line == "\n"

    # take all the rest of the lines as the message
    message = line
    while line = file.gets
      message << line
    end

    # write parsed info to output (for log file)
    puts address
    #puts last_date
    #puts subject
    #puts message

    # split the e-mail address into user and domain to put into vmail path
    user = address.split('@')[0]
    domain = address.split('@')[1]
    #puts user
    #puts domain

    # build the vmail path to the new mail message files
    mail_path = "/home/vmail/#{domain}/#{user}/new"
    
    #for each new mail file
    Dir.new(mail_path).each do |mail_file|
      #don't act on the linux '.' listings
      unless mail_file[0,1] == "."
        # get mail file for reading
        mail = File.new(mail_path + '/' + mail_file)
        
        #log mail filename and mod times (for debugging)
        puts mail_file
        puts "mail_file_time: #{mail.stat.mtime}"
        puts "conf_file_time: #{last_date}"
        
        # act only if mail file is newer
        if mail.stat.mtime > last_date
        
          # initialize flags for From and Subject lines in message
          found_from = false
          found_subject = false
          
          #get first line from email message
          line = mail.gets
          begin
            # regex to check if line starts with 'From:'
            # if so, store as from
            if line =~ /^From:/i && !found_from
              found_from = true
              from = line
            end
            
            # regex to check if line starts with 'Subject:'
            # if so, append the base_subject from above and store as subject
            if line =~ /^Subject:/i && !found_subject
              found_subject = true
              subject = "Re: #{line.chomp} - #{base_subject}"
            end
            
            #get next line
            line = mail.gets
          # loop over lines until both from AND subject are foud OR there are no more lines
          end while (!found_from && !found_subject) || line
          
          # use the base subject if couldn't find message subject
          subject = base_subject unless found_subject

          
          #puts "From line: #{from}"
          
          # take the From line and split on spaces
          # each 'word' which contains an @ (and isn't mailer-deamon) store into the to array
          to = Array.new
          from.split(' ').each do |name|
            if(name =~ /@/ && !(name =~ /MAILER.DAEMON/i))
              to << name
            end
          end

          # create comma separated list of email addresses; stripping angle brackets
          to = to.join(',').gsub('<','').gsub('>','')

          # log to whom sending mail
          puts "Sending to: #{to}"
          
          # don't send mail to nobody
          unless to == "" || to == 'invalid@emailaddress.com'
            Pony.mail(:to => to, :from => address, :subject => subject,
              :body => message )
          else
            puts "To is invalid"
          end
        else
          # log that the mail file is not newer than the conf file
          puts "From: #{mail_file} is not newer"
        end
        # separate the mail messages in the log
        puts "------------\n\n"
      end
    end
    
    # update the auto response file's mod time
    FileUtils.touch "#{conf_path}/#{response_file}"
  end
end

Posted by Chris M

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

(required)

 

Trackbacks are disabled.