Simple Email Form with ActionMailer

Working with ActionMailer is a bit different from working with ActiveRecord. While it makes sense to me that you would have a model for the data of the email message you send, the flow of control seems inverted from the rest of Rails. With ActionMailer call the model from the controller, then the model triggers the view code, rather than rendering the view from the controller with the model data as is typical with Rails. From the ActionMailer docs: “Emails are defined by creating methods within the model which are then used to set variables to be used in the mail template, to change options on the mail, or to add attachments. ” What follows is the steps that I followed to create a webform that sends an email in Rails, which turned out to be fairly simple, but it did take some digging to put the pieces together, so I figured other folks might find them useful.

Set up
This exercise requires Ruby 1.8.7 and Rails 2.2.1 or later to use TLS (required for Gmail SMTP). For older versions of either Ruby or Rails, you can try a plugin like action_mailer_optional_tls. There is a nice screencast introductory tutorial that runs through a subset of the following steps and is definitely worth watching.

To set up the context for this example, imagine we’re creating comment forms for a news website, where on each article page there is a comment form. To simulate this, we’ll just scaffold up some article pages:

$ rails mailer_example
$ ./script/generate scaffold article title:string author:string body:text
$ rake db:migrate

Test-Driven
In test-driven style (thanks to rubytutorials.net), here’s a spec for we’ll make work (at least the sending mail part of it). Note that “from” doesn’t meet expectations in 2.3.3 and 2.3.4 it is an array, rather than a string, but it has been fixed for a future release.

spec/models/notifier_spec.rb

equire File.dirname(__FILE__) + '/../spec_helper.rb'
describe Notifier do
  before(:each) do
    ActionMailer::Base.delivery_method = :test
    ActionMailer::Base.perform_deliveries = true
    ActionMailer::Base.deliveries = []
  end
  it "should send feedback email" do
    comment = {:name => "Name", :email => "[email protected]", :body => "nice article!"}
    mail = Notifier.deliver_feedback(comment)
    ActionMailer::Base.deliveries.size.should == 1
    mail.body.should =~ Regexp.new(comment[:body])
    mail.subject.should =~ Regexp.new(comment[:name])
    #mail.from.should =~ Regexp.new(comment[:email])
  end
end

Mailer Configuraton
Typically the settings would be different per environment and would be specified in config/environment/development.rb, etc. For this exercise, I just modified the main environment.rb file.

in config/environment.rb:

Rails::Initializer.run do |config|
	:
	:
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    :address => "smtp.gmail.com",
    :port => 587,
    :domain => "gmail.com",
    :user_name => "[email protected]",
    :password => "yourpassword",
    :authentication => :plain,
    :enable_starttls_auto => true
end

Generator
ActionMailer has a generator which creates files for the model and a view file, along with a unit test which is a lot less blank that the ActiveRecord unit tests.

$ ./script/generate mailer Notifier feedback
      create  app/models/
      create  app/views/notifier
      create  test/unit/
      create  test/fixtures/notifier
      create  app/models/notifier.rb
      create  test/unit/notifier_test.rb
      create  app/views/notifier/feedback.erb
      create  test/fixtures/notifier/feedback

Model
Edit the model, so that it takes a hash as it’s first parameter (we’ll end up getting this from the form).

class Notifier < ActionMailer::Base
  def feedback(comment, sent_at = Time.now)
    subject    "feedback from #{comment[:name]}"
    recipients "[email protected]"
    from       "\"#{comment[:name]}\"<#{comment[:email]}>"
    sent_on    sent_at
    body       comment
  end
end

Controller and Views
Rename the file “feedback.erb” to “feedback.html.text.erb” This will be the template used for the email, formatted in HTML. Had we wanted to send text-only emails, the file would have been called feedback.text.plain.erb. Rails sets the content type of the email to be the one in the filename. Or we could supply both and Rails will generate a “multipart/alternative” message with both template rendered for the html and plain mime parts.

Next we’ll create a form in the article show page and a controller action that will send the email.

in /views/articles/show.html.erb

<% form_tag :action => "feedback", :id => @article.id do -%>
<table>
    <tr>
        <td><label for="comment_name">Name:</label></td>
        <td><%= text_field "comment", "name", :size => 30 %></td>
    </tr>
    <tr>
        <td><label for="comment_email">Email Address:</label></td>
        <td><%= text_field "comment", "email", :size => 30 %></td>
    </tr>
    <tr>
        <td><label for="comment_body">Body:</label></td>
        <td><%= text_area "comment", "body", :rows => 8, :cols => 30 %></td>
    </tr>
</table>
<input type="submit" value="Send Feedback" class="primary" />
<% end -%>

in /controllers/articles_controller.rb

  def feedback
    article = Article.find(params[:id])
    article_info = {:title => article.title, :author => article.author, :id => article.id}
    comment = params[:comment].merge(article_info)
    Notifier.deliver_feedback(comment)
    render :text => "Thank you for your feedback"
  end

Now you should have a form that sends email. Yay!

3 Comments

  1. Posted June 7, 2010 at 9:47 am | Permalink

    Great tips! I will try it definitely
    thanks for sharing this!

  2. Posted January 30, 2011 at 1:55 am | Permalink

    Respect to post author, some good selective information .

  3. Posted February 11, 2011 at 6:37 pm | Permalink

    Excellent write-up, especially the portion on testing. I followed the Railscasts mailer recipe, which did not include tests and was struggling with testing mailer. Thanks!

Post a Comment

Your email is never shared. Required fields are marked *

*
*