DRY Internal and External Notifications using ActionMailer

I know that most Rails developers have come across the problem of notifying people by email that they have an internal message on your app’s own messaging system. Usually this is easy enough, and elegantly done in your model by using

class InternalMessage < ActiveRecord::Base
  after_save :notify_person_of_new_internal_message
  
  protected
  def notify_person_of_new_internal_message
     MyMailer.deliver_new_posting(self.from_person, self.message)
  end
end

However, let’s consider a situation where you need duplication of data on two different processes. For example, if you would like to generate a notification message when some posts to a common forum. The quick way to do this would be by calling two separate methods in your controller, like so:

subject = "Ted Stevens has posted a new topic"
body = "From Ted Stevens: " +
        "'I hope you get this, the tubes have been acting up recently!'"
@person.save_to_inbox(subject, body)
MyMailer.deliver_new_posting(@person, subject, body)

This requires you to generate the subject and the body of the message and use the methods to work out the logic.

However, with a little ActionMailer hackery, you can make this scheme much cleaner.

MyMailer.notify(person, :new_posting, from_person, note)

In your model,

class MyMailer < ActionMailer::Base
  class << self
     def notify(person, method, *params)
        new_mailer = new
        new_mailer.create!(method, *params)
        mail = new_mailer.mail
        person.save_to_inbox(mail.subject, mail.body)
        deliver(mail) if person.wants_email_notification?
     end
  end

  def new_posting(from_person, note)
     from from_person.email
     subject "#{from_person.name} has posted a new topic"
     @body[:from] = from_person
     @body[:note] = note
  end
end

Next, in views/my_mailer/new_posting.rhtml

From <%= @from_person %>: <%= @note %>

The following three lines generate a new MyMailer object, create the email specified by the method (in this case, :notify_posting) using ActionMailer’s create! method (which in turn uses Ruby’s glorious send method), and pull out the TMail object that ActionMailer sends to the SMTP server. Conveniently, the TMail object lets you extract out the subject and body, which lets you map them to your InternalMessage class.

new_mailer = new
new_mailer.create!(method, *params)
mail = new_mailer.mail

The above code generates the email, pulls out the generated subject and the body and uses it as the subject and body of the internal message. The idea is that the content of both the email notification and the internal email match up. This method does not require you to setup your subject and body. Instead, this method lets you use ActionMailer as a rendering framework for generating text/html for all of your internal messages. DRY is Good!