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