Tuesday, March 23, 2010

Rails 3 Action Mailer Example

Update 2012-02-19: This tutorial has been tested using Rails 3.0.11. Source code is available on Github: https://github.com/kuntoaji/rails_3_contact_form

Create Rails app
rails new my_app
cd my_app

Create mailer
rails g mailer notifier

Create Support Resource
rails g resource support

Remove Support's migration (we don't use database)
rm db/migrate/20100323085113_create_supports.rb

Configure ActionMailer to send email via Gmail
# config/initializers/mailer.rb
ActionMailer::Base.delivery_method = :smtp

ActionMailer::Base.smtp_settings = {
:enable_starttls_auto => true,
:address => 'smtp.gmail.com',
:port => 587,
:domain => 'yourdomain.com',
:user_name => 'administrator[at]yourdomain.com',
:password => 'yourpassword',
:authentication => 'plain'
}
# app/mailer/notifier.rb
class Notifier < ActionMailer::Base

  def support_notification(sender)
    @sender = sender
    mail(:to => "administrator[at]yourdomain.com",
         :from => sender.email,
         :subject => "New #{sender.support_type}")
 end
end
# app/views/notifier/support_notification.html.erb
hello world!
<%= @sender.content %>
# config/routes.rb
resources :supports, :only => [:new, :create]
# app/controller/supports_controller.rb
class SupportsController < ApplicationController
  def new
    # id is required to deal with form
    @support = Support.new(:id => 1)
  end

  def create
    @support = Support.new(params[:support])
    if @support.save
      redirect_to('/', :notice => "Support ticket was successfully sent.")
    else
      flash[:alert] = "You must fill all fields."
      render 'new'
    end
  end
end
# app/models/support.rb
class Support
  include ActiveModel::Validations

  validates_presence_of :email, :sender_name, :support_type, :content 
  # in order to work with form, you must have an id attribute
  attr_accessor :id, :email, :sender_name, :support_type, :content

  def initialize(attributes = {})
    attributes.each do |key, value|
      self.send("#{key}=", value)
    end
    @attributes = attributes
  end

  def read_attribute_for_validation(key)
    @attributes[key]
  end

  def to_key
  end

  def save
    if self.valid?
      Notifier.support_notification(self).deliver!
      return true
    end
    return false
  end
end
# app/views/supports/new.html.erb

Contact Us

<%= render 'form' %>
# app/views/supports/_form.html.erb
<% form_for @support, :url => { :action => "create" }, :html => { :method => :post } do |f| %>
  <p>
    <%= f.label "Name" %>
  </p>
  <p>
    <%= f.text_field :sender_name %>
  </p>
  <p>
    <%= f.label "Email" %>
  </p>
  <p>
    <%= f.text_field :email %>
  </p>
  <p>
    <%= f.label "Type" %>
  </p>
  <p>
    <%= f.select :support_type, options_for_select(["Bug", "Ask", "Idea"]) %>
  </p>
  <p>
    <%= f.label "Details" %>
  </p>
  <p>
    <%= f.text_area :content %>
  </p>
  <p>
    <%= f.submit "Submit" %>
  </p>
<% end %>

Raise Exception When Mailer Can't Send the Email (Development mode)
# config/environments/development.rb
# ...
config.action_mailer.raise_delivery_errors = true
# ...

Done! To try this code, open http://localhost:3000/supports/new or try the code via Rails console.
support = Support.new(:email => "youremail[at]yourdomain.com", :sender_name => "my name", :support_type => "bug", :content => "this is my support")
support.save

53 comments:

  1. I had to add
    gem 'mail'
    to my Gemfile then run
    bundle install

    ReplyDelete
  2. undefined method `to_key' for #1}>

    ReplyDelete
  3. I'm guessing this is deprecated? I've been working at this for a couple hours and there are quite a few reasons why this does not work anymore.

    ReplyDelete
  4. @depassion & @anonymous: Sorry, this tutorial used Rails 3 Beta 1. Not tested yet for Rails 3 final version.

    ReplyDelete
  5. Tested on Rails 3.0.0 final version.

    ReplyDelete
  6. Its a great tutorial, and I've followed it several times over the last few months, but there are a couple of bugs in the code that I feel I should point out.

    In the section titled "Remove Support's migration (we don't use database)" you advise to run "rm db/migrate/20100323085113_create_posts.rb" - which will totally break everything because this code removes the Posts migration.... You probably meant for the user to run "rm db/migrate/XXXXXXXXXXX_create_supports.rb" .... :)

    In the section titled "Sending email via Gmail" you provide some code to be pasted into application.rb, but there is a trailing comma at the end of the hash and this will cause mayhem too. I would personally advise sticking that code into a mailer.rb intiliazer file to keep the application file clean, but at the very least, I would remove the trailing comma from the hash....

    Anyway, thanks for the tut, as I said, I've followed it a few times now

    ReplyDelete
  7. Thanks for you correction and advise Stephen. I'll update it.

    ReplyDelete
  8. I tried this but got an error:
    NoMethodError: undefined method `support_notification' for Notifier:Class

    Any thoughts?

    I am using rails 3.0.3

    ReplyDelete
  9. stalkert, it's no method error, so you must define support_notification method on Notifier class.

    the code on this tutorial works fine with rails 3.0.3

    ReplyDelete
  10. thanks, it works well with 3.0.3

    ReplyDelete
  11. Cant get it working, having this error: http://pastie.org/1437694

    everything is same as showed here, Please can anybody help!

    ReplyDelete
  12. Great tutorial! Many thanks. What do I need to do if I want to use a database to store supports?

    ReplyDelete
  13. Having the almost the same issue as soend https://gist.github.com/823565 error is there if anyone can help please let me know thank you.

    ReplyDelete
  14. Excellent write-up, it is walks through all the steps of the ActionMailer setup and useful for helping get started.

    ReplyDelete
  15. Thanks for this article. Very helpful.

    ReplyDelete
  16. Hi there. I've followed your great tutorial. For some reason, it isn't sending the email for me. The server log is showing that the email has been sent, but I'm not getting an email in my account when testing from localhost. Is there a setting I need to change? I'm sure I have the email address correct (it is an account managed by gmail). I can get other email to this account. Thanks!

    ReplyDelete
  17. Julie

    Try opening config/environments/test.rb and change this line:

    config.action_mailer.delivery_method = :test

    to:

    config.action_mailer.delivery_method = :smtp

    This should allow you to send real emails with rails 3.0.5 assuming you followed it closely. I just got the tutorial working 100% with the latest updates.

    If you leave the line as-is it stores outgoing emails in an array, which could still be viewed if you didn't want to send real emails.

    ReplyDelete
  18. Works great, Thank you! Tested on Rails 3.0.3

    ReplyDelete
  19. For some inexplicable reason, I could not get the controller to include the model without explicitly requiring it. I thought this was automagic?

    ReplyDelete
  20. uninitialized constant Support::Notifier

    app/models/support.rb:25:in `save'
    app/controllers/supports_controller.rb:8:in `create'

    ...any ideas?

    Thanks!

    ReplyDelete
  21. I'm having the same exact same problem as anonymous posted above:

    uninitialized constant Support::Notifier

    app/models/support.rb:25:in `save'
    app/controllers/supports_controller.rb:10:in `create'

    How did other people deal with this?

    ReplyDelete
  22. @bludragon, anonymous, & apeiron: what rails version do you use?

    ReplyDelete
  23. I had the same error: uninitialized constant Support::Notifier. Restarted the server and it worked.

    ReplyDelete
  24. Has anyone had any problems with not finding a table? I used Contact, rather than support and am getting the following error; Could not find table 'contacts' from my form?

    ReplyDelete
  25. Hi ReddoBowen

    Probably because you kept the line :
    "class Contact < ActiveRecord::Base" inside the app/models/contact.rb file. Because an ActiveRecord inherited class will automaticly search for a table ;).

    You should delete the " < ActiveRecord::Base" part so that you have those two lines instead :

    class Contact
    include ActiveModel::Validations

    Hope it helps :)

    ReplyDelete
  26. I changed it from support to contact, and although I believe the code is all correct, it will not render the form partial for the contact controller.

    What could be causing this?

    ReplyDelete
  27. @Mike, its just missing a = after the initial <%, as in

    <%= form_for @support, :url => { :action => "create" }, :html => { :method => :post } do |f| %>

    ReplyDelete
  28. Hi, I was just doing this great tutorial and as for the command prompt it's working and sending me the email. Problem is, I never receive any emails. Any suggestions why that could happen? I already did the changing suggestet some comments above:

    Try opening config/environments/test.rb and change this line:

    config.action_mailer.delivery_method = :test

    to:

    config.action_mailer.delivery_method = :smtp

    and I also got rid of the mistake that was pointed out just two comments earlier some time ago. So it's not giving me any mistake massages, it says it's working and sending the mail... I really don't have any idear anymore, why I still don't get the emails.

    Maybe someone here has an idear?

    ReplyDelete
  29. @Ithil: Have you checked the spam inbox?

    ReplyDelete
  30. Yes, the mails don't go into my spam inbox either. I had the same smtp settings in my PHP form and there everithings working fine and I receive the emails...

    ReplyDelete
  31. @Ithil: Could you provide your mailer configuration and log file to me?

    ReplyDelete
  32. I don't get any errors..It shows the success flash message but i dont get any mail from the contact form. I am using Rails 3.0.7

    Any suggestions. I followed all the above steps

    ReplyDelete
  33. maybe your username or password is incorrect. try to change deliver to deliver! to debug mailer:

    Notifier.support_notification(self).deliver

    to

    Notifier.support_notification(self).deliver!

    ReplyDelete
  34. If I try to visit /supports/new I get the message:

    Table 'appname_development.supports' doesn't exist

    -> I deleted the migration file but in some way rails thinks he needs the db table for this? any ideas?

    ReplyDelete
  35. In order to pass the text you should also delete the fixture support.yml

    ReplyDelete
  36. Great tutorial! I went to implement it, and hit a bug after I hit the 'submit' button on a test email:

    NoMethodError in SupportsController#create

    You have a nil object when you didn't expect it!
    You might have expected an instance of ActiveRecord::Base.
    The error occurred while evaluating nil.[]

    app/models/support.rb:16:in `read_attribute_for_validation'
    app/models/support.rb:23:in `save'
    app/controllers/supports_controller.rb:9:in `create'

    ReplyDelete
  37. @dietcoupon:
    change
    class Support < ActiveRecord::Base

    to

    class Support

    ReplyDelete
  38. Hi! Getting this error with Rails 3.1. (using #script ttag to be able to comment here)

    /test/app/mailers/notifier.rb:6: syntax error, unexpected tIDENTIFIER, expecting ')'
    ...askineriet.se #script type="text/javascript">
    ... ^
    /test/app/mailers/notifier.rb:6: syntax error, unexpected tSTRING_BEG, expecting keyword_do or '{' or '('
    ...e#script type="text/javascript">
    ... ^
    /test/app/mailers/notifier.rb:8: syntax error, unexpected tIDENTIFIER, expecting keyword_end
    ...nt.getElementById("__cf_email__");a=l.className;if(a){s='';r...
    ... ^
    /test/app/mailers/notifier.rb:12: syntax error, unexpected ')', expecting keyword_end

    ReplyDelete
  39. The save method in the model is failing for me when the submission is invalid. Apparently Rails now looks for a template named the same as the controller action if the model is returning false. Any ideas?

    I am getting a Template Missing error:

    Missing template supports/create

    This is only happening if I attempt to post invalid data, otherwise it works great!

    ReplyDelete
    Replies
    1. @Shane: You need to create handler for invalid data. In this tutorial, I only show how to send email with valid data.

      Delete
  40. Hi, this tutorial looks better than the rest, except I can't get mine to work. Coded everything as you did, but as soon as I hit submit on the form (/supports/new) it starts loading and then exits with an 'execution expired' error with title "Timeout::Error in SupportsController#create" What can cause this?

    ReplyDelete
    Replies
    1. try check if your mailer username & password is configured correctly

      Delete
  41. How can I make a dynamic Email notification? I need to do something like tweet, send a notification to different emails when some activity happen.

    ReplyDelete
  42. I'm getting an error when I submit the form:
    SocketError in SupportsController#create

    getaddrinfo: nodename nor servname provided, or not known

    I'm running this from localhost, although I tried 127.0.0.1 as well. I tried changing my ports in mailer.rb. Do I need to be running in production mode?

    ReplyDelete
    Replies
    1. no, you don't need run it in production mode. Could you give me error message from your app?

      Delete
  43. Thanks for the great article. One correction is "=" is missing at
    <% form_for @support, :url => { :action => "create" }, :html => { :method => :post } do |f| %>

    Change this to:
    <%= form_for @support, :url => { :action => "create" }, :html => { :method => :post } do |f| %>

    If you could write this same article for the latest version of RoR, it will be great.

    ReplyDelete
  44. This worked very well for me, thank you! (Rails 3.2.13)

    ReplyDelete
  45. Hello , all things work well, but user email ,doesn't come in "from:" ,it takes my email , which i set for action mailer,why this happend?

    ReplyDelete
  46. as far as I know, "from" field use settings in ActionMailer::Base.smtp_settings

    ReplyDelete
  47. Hi. Great tutorial!

    When I hit submit, I don't get the flash "sent" message, and I'm not getting anything on my email either. The browser status just says "connecting" and then nothing else happens.

    I checked the SMTP config and everything looks fine (user, password, port)...any ideas why this could be happening?

    Thanks!

    ReplyDelete
    Replies
    1. have you checked your spam inbox? if email is still does not exist, try to run the code via rails console

      Delete

© Railsmine