Receiving emails with Ruby on Rails
I spent the weekend getting a little application to play nicely with emails being sent to it. As receiving emails with Rails is something that is not written about as much as sending emails, I thought I would share my solution.
This tutorial will show you how to receive emails into your application, store them in the database and forward the contents to multiple recipients.
The first problem of course is getting the emails into your application. I have root access to my server, so my solution uses Postfix. If you don’t have root access, you can use POP and there are a number of examples out there. On with the instructions…
As I am hosting a number of domains on the same box, I set up my main.cf file as follows:
1 2 |
virtual_alias_domains = mydomain.com
virtual_alias_maps = hash:/etc/postfix/virtual |
I then set up my virtual file (which you may need to create) with a single line:
@mydomain.com rails_mailer |
This will intercept any emails sent to mydomain.com and pass them to the rails_mailer alias, which is created in your alias file. Mine is located in /etc/aliases: yours might be in /etc/postfix/aliases. Add to the bottom of your aliases file:
rails_mailer: "|curl -F mail='<-' http://mydomain.com/emails" |
This uses curl to post the mail as form data to the given url. The email contents will be stored inside the mail parameter. The pipe character at the start indicates that this is a shell command and not a directory.
To apply all these changes:
1 2 3 |
sudo postmap /etc/postfix/virtual sudo newaliases sudo /etc/init.d/postfix reload |
To check that this is working, change into your /var/log directory and use:
tail -f mail.info |
Then send an email to any address @mydomain.com and you should see it received in your mail log and passed to your Rails app. If you look in your app’s logs, you should see it spitting out a 500 error as there is no code to handle the email…yet.
On to the Ruby. Create an Emails controller and define a create method:
1 2 3 4 5 |
def create if params[:mail] Notifier.receive(params[:mail]) end end |
This receives the contents of params[:mail] and passes it to the receive method of our notifier model.
To deal with Rails’ cross-site scripting protection, you need to add to the top of your controller:
skip_before_filter :verify_authenticity_token |
Next, generate a mailer called notifier. In notifier.rb, define a method called receive(email). Using this method name automatically invokes the power of TMail. My receive method is as follows. You will need to change this to make sure you pull the information you need from the email:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def receive(email) event_name = email.to event_name = event_name.to_s.sub('@mydomain.com', '') event = Event.find_by_tidy_name(event_name) body = email.body address = Array.new event.attendees.each do |attendee| address << attendee.email.to_s end from = email.from.to_s new_email = event.emails.new new_email.from = email.friendly_from new_email.body = body new_email.save Notifier.deliver_event_email(event, from, address, body) end |
Before running through this code, I should explain that when people use the app, they receive a dedicated email address in format myeventname@mydomain.com.
This code does two things. Firstly, it extracts the following data from the email: the name of the event, the body of the email and who the email is from. It then constructs a new email that belongs to the event and saves to the database. It then invokes another method in our notifier.rb to forward the email body to the individuals who are attending the event.
1 2 3 4 5 6 |
def event_email(event, from, address, body) setup_email(from) @bcc = address @subject += "#{event.name}" @body[:body] = body end |
In terms of the application that I wrote this for, I hope to launch it within the next week. If you’ve got any questions or run into any problems, leave a comment and I’ll try and help.