Sending email in Ruby on Rails

So I have recently become acquainted with Ruby on Rails and have been playing around with some of its functionality. One of the things I spent the most time trying to figure out was how to get my application to send mail correctly. After following the instructions on all the existing sites out there I had it working in my development environment but found that it crapped out when I tried to port it to a production environment. So I decided to throw something together to explain the steps I went through to get everything working.

Disclaimer – I am a Java guy by trade and am new to RoR. If I am doing something “the hard way” please speak up RoR gurus.

First, the background. Ruby on Rails handles most stuff for you – the object relational model (ORM), database transactions, views, and even mail. The mail is composed of a couple parts: configuration, a model, a view, and a client. Lets step through these.

The configuration is simply information that goes into either the config/environment.rb or the config/environments/[production || development || test].rb file. If you use one of the files in the config/environments folder you will be able to specify multiple settings for multiple environments. For example, I set mine up so that config/environments/development.rb pointed to my localhost mail server while my config/environments/production.rb pointed to my hosted mail server. Here is the basic information you’ll need:

ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.raise_delivery_errors = true
ActionMailer::Base.smtp_settings = {
  :address => "smtp.myserver.com",
  :port => 25,
  :user_name => "myusername",
  :password => "mypassword",
  :authentication  => :login
}

Next, you will need a model that contains the actual functionality for sending the mail and interacting with the ActiveMailer. The easiest way to do this is to run “ruby script/generate mailer mymailertest” (for non-windows drop the “ruby”). This will produce the following files:

exists  app/models/
create  app/views/mytestmailer
exists  test/unit/
create  test/fixtures/mytestmailer
create  app/models/mytestmailer.rb
create  test/unit/mytestmailer_test.rb

Before we start describing the parts, lets take a look at how this all works. When the client (usually a controller, but really can be any part of the code) wants to send an email they will invoke the mailer model, calling a specific method. This method will set up the email then forward to a view, which renders the contents of the email. This rendered content is what is actually sent over the wire.

We now need hook the model up so that it will work. The model, mytestmailer.rb, already extends ActionMailer so we just need to add a method. This method will be called later to deliver mail. The method we define will be mapped to a view that will actually be the email template. We will explain that next. First, add a method to your model that will represent an action taken by your application – welcome, confirmation, invalid login attempt, etc. An important note in this step is that if you want your arriving email to display your/a name instead of the email address you will need to use the notation used in the from line below, since the from line equates to the SMTP FROM header. Also, this assumes that you have a User or some other object that you wish to pass to the view in order to render the email.

class Mytestmailer < ActionMailer::Base

  def welcome(user)
    recipients "somename@somedomain.com"
    from "Your Name "
    subject "RoR Test Email"
    body :user => user
  end

end

This will automatically forward to a view with the same name as the method, in this case app/views/mytestmailer/welcome.html.erb (note that if you are using a version of RoR prior to 2.* you will have an extension of *.rhtml – this makes no difference). Put simply, the model is the contents of the email only with scripting functionality built in. In our scenario, the model will pass in a User object which we will use to construct the email. Our view looks like this:

Dear <%= @user.first %> <%= @user.last %>,

Thanks for signing up for the McDonaldLand mailing list!

We are here to help so if you ever need us blah blah blah.

Thanks for your interest in McDonaldLand,
Jason McDonald

So we are almost done. We just need to set a client up to call the mailer. From the client we would simply invoke the deliver_* method on the mailer object, which would then delegate to the appropriate method. In our example we would call deliver_welcome, which would route to the welcome method. The arguments passed here will be routed to the mailer model object as well. So to send our email from whatever part of the code we want all we have to do is this:

mytestmailer.deliver_welcome(@user)

That’s it.


Backwards compatible languages are a myth

In the theoretical sense, the notion of backwards compatibility in programming languages is sound. However, when we try and apply this theory on a universal scale we begin to see a number of tiny breakdowns that point towards the reality that the concept of backwards compatible software languages, at least from a universal perspective, is a myth.

One of the most notorious of languages to embrace the concept of preserving backwards compatibility is Java. However, Java does not absolutely preserve backwards compatibility, despite their claims. The most prominent example of this is in the upgrade from J2SE 1.4 to 1.5. Anyone use ‘enum’ as a variable name in their code? If so you broke. Perhaps I am misinterpreting the true meaning of backwards compatible, but this does not seem to fit.

However, just because your application falls into the realm of a backwards compatible one doesn’t mean that you are set. Any application that uses a third party library that proves to not be backwards compatible is doomed as well. Unless there is a new version of your library that integrates with the upgraded language, you have the ability to fix the library, or have a substitute library you can use, your upgrade is doomed.

The concept of backwards compatibility is a good one, however everything should be in moderation. When the adherence to this rule comes at the cost of the proper implementation of language features, as Bruce Eckel discusses, it can be argued that the notion of backwards compatibility is more of a long term bane than blessing. If the features of the language upgrades are good enough people will upgrade, regardless of whether they have to do a little more work. One look to the recent Ruby or Python additions are proof of this.


Site was down

Sorry to anyone who tried to access my site earlier. My host seems to have had a moment of incompetence.

You can read the transcripts of this drama by clicking the “Ticket #” links as you read along.

I have been in contact with them lately (Ticket 1) trying to get everything working so that I can host a Ruby on Rails application and have been having quite a bit of trouble. I finally figured out how to get the apps up to the server, configured everything, then it wouldn’t start. So I filed a ticket last night (Ticket 2) to fix this. Everything was working fine this morning with my ruby files sitting on the server, useless.

Around noon today (queue the tension building music) I went to my site to find a generic site is having difficulties page. What!?!? My bandwidth is fine, I didn’t violate any terms of service, the server is up – what is going on? So I file another ticket (Ticket 3) and the struggle really begins. I ended up waiting half an hour before picking up the phone and calling, where I spent another 40 minutes on the phone waiting for an answer. The answer I got on the phone was that it was a mistake and they were fixing asap, which differs greatly from Ticket 3.

I am speculating here but am relatively certain that I am right about what transpired. The support crew changed some things so that my RoR applications would work then launched my ruby apps on my behalf. This caused a CPU spike, which I knew nothing about b/c I had changed nothing, that caused them to suspend my account.

Four hours later and my site is back up. I have removed the links to my host from my blogroll and plan on cancelling my reseller account through them. I sent an email to the management email address detailing the problems I have had but I am still really pissed about this.

Can anyone recommend a GOOD host that will support at least PHP and Ruby on Rails? Java too would be nice but I know that is pushing it…

=========================

2/13/2008 Update

I never heard back from the “Manager’s queue” and found my site to be down again today. They did manage to fix the problems listed in this post and got me up and running, however to this day I am apprehensive about putting any ruby apps on my system. I would not recommend ANHosting or MidPhase. The price is great but the service sucks.