I came across this post via @dhh’s twitter stream. Where Robby Russell describes his decision to trigger email delivery from model callbacks. It sparked a heated debate in the comments over the proper place for code that sends an email. My addition to the discussion quickly outgrew the size of a reasonable comment, and follows as this post.

When determining which component to send email notifications from, the distinction between Model, Controller and Observer is just a case of splitting hairs over semantics. There are valid arguments for it to go in either of the three spots.

In the controller it looks like this:

if @customer.save
  CustomerMailer.deliver_welcome_message(@customer)
  flash[:message] = "Your account has been successfully created. We've sent you a welcome letter with..."
  redirect_to dashboard_path
else
  ...
end

In the model it looks like this:

after_create :send_welcome_message #, other callbacks..
 
def send_welcome_message
  CustomerMailer.deliver_welcome_message(self)
end

In an observer it looks like this

def after_create(customer)
  CustomerMailer.deliver_welcome_message(customer)
end

Sending emails feels like business logic belonging in the model because it can happen asynchronously from HTTP requests, and often accompanies record updates. However this is not always the case, and the decision to send an email may be made in the controller.

Regardless of how you use ActionMailer, it is still written like a controller (despite its subclasses existing in app/models). It splices together model and view (template) from a request to send a response. The big difference between ActionMailer and ActionController is that ActionMailer’s requests and responses do not share source and destination. Anything can send ActionMailer a request, but the response in the form of Email over SMTP can be completely unrelated to the request.

What the before/after_save callback really accomplishes is triggering an ActionMailer request. If you treat ActionMailer like any other controller the choice becomes clear.

Placing the code in the model runs contrary to the MVC workflow. Placing it in the controller is not much better. Controllers should not be communicating directly with each other, the best you get is redirection. Which leaves the observer.

Triggering an email delivery form an observer makes the most sense. Even if there is functionally no difference between doing it from the callback in the model or an observer of the model. In my mind the difference between a model callback and an observer callback depends on the answer to question: “Does this callback need to succeed before the action can proceed?” If the answer is yes, it belongs in the model, otherwise it goes in an observer.

For those of you that argue about the dangers of a callback triggering when you don’t want it to. Such as when creating an administrator There’s always a way around that. I prefer the attr_accessor method.

class User < ActiveRecord::Base
   ...
   attr_accessor :cancel_delivery
end
 
class UserObserver < ActiveRecord::Observer
  def after_create(user)
     UserMailer.deliver_welcome_message(user) unless customer.cancel_delivery
  end
end

Now the create won’t send an email. And not just because of the invalid email address I used in the example to throw off spam bots.

User.create(:email => "emery@foo.bar", 
   :name => "Emery", :cancel_delivery => true, :type => "administrator")

If you’ve subclassed Administrator from User this becomes even more DRY with another callback:

class Administrator < User 
  before_validation_on_create :set_cancel_delivery
  ...
 
  protected 
  def set_cancel_delivery
    self.cancel_deliver = true
  end
end

With this most recent change the following will not send an email:

Administrator.create(:name => "Emery", :email => "emery@foo.bar")