» tagged pages
» logout
Rails
Return to Dave Naffis - Rails, Ruby, Randomness

Receiving Emails and Attachments with Rails

Tags Applied to this Entry

1 person has tagged this page:

Does your Rails app need to handle incoming emails with the attachments? All of the examples I’ve seen so far show you how to insert email attachments into the DB. Here’s a quick example that uses RailsCron to poll a POP3 account every minute for new emails and stores the attachments on the filesystem. If you need help using or running RailsCron see my previous posts about the topic.

The Agile book has a good example that kicks off a runner script but I think this method is far more efficient than having your mail system kick off a separate runner every time a new email is received, especially if you’re dealing in high volume.

It also handles non-responsive or slow responding POP3 servers by setting a high timeout length and retrying a handful of times before it gives up.

This class will check for and hand off any incoming emails:

require 'net/pop'

class EmailQueue < ActiveRecord::Base
  background :poll_mail, :every => 1.minute, :concurrent => false      

  def self.poll_mail
    retrycount = 0
    begin
      timeout(600) do
        Net::POP3.start("yourdomain.com", nil, "username", "password") do |pop|
          if pop.mails.empty?
            logger.info "NO MAIL" 
          else
            pop.mails.each do |email|        
              begin
                logger.info "receiving mail..." 
                AssetSubmitHandler.receive(email.pop)
                email.delete
              rescue Exception => e
                logger.error "Error receiving email at " + Time.now.to_s + "::: " + e.message
              end          
            end
          end
        end
      end        
    rescue TimeoutError      
      if(retrycount < 5)
        retrycount+=1
        retry
      else
        logger.info("ERROR Timeout error in poll_mail attempt #" + retrycount.to_s)
        nil
      end
    end  
  rescue Exception => exception
    SystemNotifier.deliver_exception_notification(exception)   
    logger.info("Error in poll_mail")
    logger.info(exception.class.to_s + " " + exception.message.to_s + " " + exception.backtrace.to_s) 
  end    

end

And here’s the code that handles the email:

class AssetSubmitHandler < ActionMailer::Base  

  # content type should be validated to image/gif, image/jpg, or image/jpeg
  def receive(email)
    if email.has_attachments?
      email.attachments.each do |attachment|
        asset = Asset.new
        asset.submitter = email.from.first        
        asset.name = base_part_of(attachment.original_filename)
        asset.content_type = attachment.content_type.chomp

        base_dir = "/home/someapp/www/"           

        # save original file
        asset.original = "assets/o_#{Time.now.utc.to_i}#{rand(1000000)}."+asset.name
        File.open(base_dir+asset.original,File::CREAT|File::TRUNC|File::WRONLY,0666){ |f|
          f.write(attachment.read)
        }

        asset.save
      end
    end
  end

  def base_part_of(file_name)
    name = File.basename(file_name)
    name.gsub(/[^W._-]/, '')
    sanitize_filename(name)
  end

  # Fixes a 'feature' of IE where it passes the entire path instead of just the filename
  def sanitize_filename(value)
    #get only the filename (not the whole path)
    just_filename = value.gsub(/^.*(\\|\/)/, '')
    just_filename.gsub(/[^\w\.\-]/,'_') 
  end

end
Username:
Password:
(or Cancel)