Understanding Action Mailer
When building a web application you should think about all the interactions/events that comprise your site. Your goal is generally to have a high user retention rate. As a developer/website owner, you want your users to interact as much as possible on your website without feeling the need to leave or use other third party services.
Let's take a simple (but important) example: Mailing.
Imagine for a second that you are a user of a particular website and you need to send an email to the technical team about a bug or something (who cares... at this point you just want to contact them). It would be kind of (super) annoying to copy and paste the websites contact email into your own personal email service (Gmail, Outlook, etc.). Again, don't forget that your goal is to KEEP the user on your website. You don't want them to open up Gmail... No! What you need is a simple way for users to contact you directly via the website.
Today I am going to show you how you can implement such feature using Action Mailer in your Ruby on Rails web app. Go take a look at the Action Mailer Basics documentation to familiarize yourself with the different features it provides.
User Authentication
Let's take this step-by-step. If you are already using the Devise Gem to authenticate users, skip this part and go right to the User Mailer section.
----Step 1
In you Gemfile
:
gem 'devise'
Then run bundle install
. After your done run rails generate devise:install
which generate an initializer that will have all the devise configuration you need along with descriptions on how to use them(config/initializers/devise.rb
).
----Step 2
Now you'll want to set up your user model. That's just one simple command :)
rails generate devise User
This generator will also configure your 7 RESTFUL routes so that it point to the Devise controller. Go take a look: config/routes.rb
.
And now, of course, run rake db:migrate
to add the new users table to your schema
!
Voila! you now have access to super useful helper methods and call backs:
before_action :authenticate_user!
---> In your controller (assuming your devise model is User). This will set a before_action that will ask for user authentication before an action. You can put it in any controller you want. For example I set up mine in my welcomeController
.
user_signed_in?
--> check if a user is signed in...
Ex:
<% if user_signed_in? %>
<li><%= link_to "My Pets", "/users/my_pets", :method => :get, class: "active" %><span class="sr-only">(current)</span></a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Sign out", destroy_user_session_path, :method => :delete, class: "active" %></li>
<%else%>
<li><%= link_to('Login', new_user_session_path)%></li>
<li><%= link_to('Sign Up', new_user_registration_path)%></li>
<%end%>
current_user
--> Helps you get info from the current user.
Ex:
<div <a href="https://github.com/plataformatec/devise#controller-filters-and-helpers" lass="form-group">
<div class="col-lg-10">
<input type="hidden" class="form-control" id="inputEmail" name="email[user_email]" value="<%= current_user.email %>">
</div>
</div>
Here I created an input field where the value is the current user's email. You'll see in a bit how this can be quite useful (hint: input type="hidden"
).
There are a bunch of other helpers but for the purpose of this blog post i'll leave them aside. Feel free to take a look at them here.
User Mailer
Now that we have our users set up, we can easily implement a mailing feature. I will now show you how you can enable your users to send emails right from your web app. Once you have a good understanding of how it works, you'll know how to send emails to who ever you want!
----Step 1
Generating a Mailer:
bin/rails generate mailer UserMailer
create app/mailers/user_mailer.rb
create app/mailers/application_mailer.rb
invoke erb
create app/views/user_mailer
create app/views/layouts/mailer.text.erb
create app/views/layouts/mailer.html.erb
invoke test_unit
create test/mailers/user_mailer_test.rb
create test/mailers/previews/user_mailer_preview.rb
As you can see, you can generate mailers just like you do for other generators. Mailers are similar to controllers in that you get a mailer, a directory for views, and a test.
Ok, now lets tackle this from the bottom up. First we need to set up our routes so that a user can 'POST' an email to a create
action in the Mailers Controller. This create
method will take the params the user passed into the form and utilise it to send the email with the appropriate content.
In config/routes.rb
post '/mailers/create' => 'mailers#create'
<form action="/mailers/create" method="post" class="form-horizontal" id= "contact-form">
<fieldset>
<legend>Adopt Me!</legend>
<div class="form-group">
<div class="col-lg-10">
<input type="hidden" class="form-control" id="inputEmail" name="email[shelter_email]" value="<%= @pet.shelter.email %>">
<input type="hidden" class="form-control" id="inputEmail" name="email[pet_name]" value="<%= @pet.name %>">
</div>
</div>
<div class="form-group">
<div class="col-lg-10">
<%if user_signed_in?%>
<input type="hidden" class="form-control" id="inputEmail" name="email[user_email]" value="<%= current_user.email %>">
<%end%>
</div>
</div>
<div class="form-group">
<label for="textArea" class="col-lg-2 control-label">Message</label>
<div class="col-lg-10">
<textarea class="form-control" rows="3" id="textArea" name="email[content]">Hello, <%= @pet.shelter.name %> I am interested in adopting <%= @pet.name %></textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default cancel-btn">Cancel</button>
<!-- only if user signed in, else alert -->
<button type="Email" class="btn btn-warning">Submit</button>
</div>
</div>
</fieldset>
</form>
What's going on here... For the purpose of this blog post, we are dealing with users who want to contact a shelter right from the pets show page. Some of our fields here are hidden (type="hidden"
). Why is that?
Well, because the user is sending an email to a particular shelter from the pet show page, we do not need to display the shelter's email. We have already associated pets with shelters where pets belong_to
a shelter and each shelter, well, has a contact email.
Here, the only thing we want the user to fill in is the actual content of the email.
<textarea class="form-control" rows="3" id="textArea" name="email[content]">Hello, <%= @pet.shelter.name %> I am interested in adopting <%= @pet.name %></textarea>
We are simply prefilling the textarea
with relevant information so as to provide the user with some context.
Awesome! look at our super params:
{"email"=>
{"shelter_email"=>"pennycat45@hotmail.com",
"pet_name"=>"Alice",
"user_email"=>"cghazanfar10@gmail.com",
"content"=>
"Hello, Lindas Feral Cat Assistance I am interested in adopting Alice"},
"controller"=>"mailers",
"action"=>"create"}
Perfect! Next step:
Let's set up our model:
class Mailer < ActionMailer::Base
default :from => 'example@email.com'
def contact_shelter(shelter_email, content, subject, user_email)
@shelter_email = shelter_email
@content = content
@subject = subject
@user_email = user_email
mail(to: shelter_email,
body: content,
subject: subject,
reply_to: user_email)
end
end
This is pretty straightforward. We have a contact_shelter
method that takes in 4 arguments, which are the values of the params passed into the form.
Jumping to our MailersController
:
class MailersController < ApplicationController
def create
@user_email = params[:email][:user_email]
@shelter_email = params[:email][:shelter_email]
@content = params[:email][:content]
@pet_name = params[:email][:pet_name]
@subject = "Interested in adopting #{@pet_name}"
Mailer.contact_shelter(@shelter_email, @content, @subject, @user_email).deliver
flash.now[:notice] = 'Message sent'
redirect_to my_pets_path
end
end
Here we are creating the params that will be passed as arguments into the contact_shelter
method.
This one line here Mailer.contact_shelter(@shelter_email, @content, @subject, @user_email).deliver
is taking care of the email delivery.
YES!! we are almost done.
Last but not least we need to set up the right email protocol
in config/environments/development.rb
to configure your mailer so that it uses the smtp protocol:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
config.action_mailer.delivery_method = :smtp
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => 'localhost',
:user_name => 'xxxxxx',
:password => 'xxxxxx',
:authentication => 'plain',
:enable_starttls_auto => true
}
You now need to create the view for your email. Create a folder under views called mailer
. In that folder create a file that is the same name of your method in your mailer model: in this case contact_shelter.html.erb
.
This is how I set up my mailer view:
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1><%= @shelter_email %></h1>
<p>
<%= @content %>
</p>
<p>Thanks!</p>
<p><%= @user_email %></p>
</body>
</html>
So the action here are the following:
--User fills in the content of the email.
--Submits the email
--email is sent to the shelters email
--Shelters can directly reply to the user
TAADAAA! Don't forget to check out Action Mailer Basics documentation if you are confused by what I am writing (It's pretty clear in my head but it might not reflect well on paper...)
Love you all and <3 Ruby
Cheers!