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:
1 2 3 4 |
$ 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 => "me@foo.com", :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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 => "yourname@gmail.com",
: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.
1 2 3 4 5 6 7 8 9 10 |
$ ./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).
1 2 3 4 5 6 7 8 9 10 |
class Notifier < ActionMailer::Base
def feedback(comment, sent_at = Time.now)
subject "feedback from #{comment[:name]}"
recipients "someone@example.com"
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<% 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
1 2 3 4 5 6 7 8 9 |
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!
5 Comments
Great tips! I will try it definitely
thanks for sharing this!
Respect to post author, some good selective information .
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!
Hello, i read your blog from time to time and i own a similar one and i
was just wondering if you get a lot of spam feedback?
If so how do you protect against it, any plugin or anything you can suggest?
I get so much lately it’s driving me crazy so any help is very much appreciated.
Great site you have here.. It’s hard to find excellent writing like yours these days. I seriously appreciate people like you! Take care!!