Action Mailer allows you to send emails from your application. There are two parts:
- A mailer class extending
ActionMailer::Base
. This works similarly to ourApplicationController
extendingActionController::Base
. Mailers live inapp/mailers
. - Views, which live in
app/views/[mailer_name]
.
$ rails generate mailer UserMailer
create app/mailers/user_mailer.rb
invoke erb
create app/views/user_mailer
invoke test_unit
create test/functional/user_mailer_test.rb
So we have the mailer, the views, and the tests.
app/mailers/user_mailer.rb
contains an empty mailer:
class UserMailer < ActionMailer::Base
default from: '[email protected]'
end
Let's add a method called welcome_email
, that will send an email to
the user's registered email address:
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
default from: '[email protected]'
def welcome_email(user)
@user = user
@url = 'http://example.com/login'
mail(to: user.email, subject: 'Welcome to My Awesome Site')
end
def reminder_email(user)
# ...
end
# other emails...
end
Somewhat like a controller, this adds a mailer action that will send a
welcome_email
to the passed user. We set the to
and the subject
(as well as from
; which is set from the default above). The content
of the email lives in a view (more in a sec) which will be rendered.
You can also set cc
and bcc
attributes. To send to multiple
emails, use an array of email strings.
You can put the name of the recipient in the email address like so:
"Ned Ruggeri <[email protected]>"
. This is a nice way of
personalizing your email. Likewise you should personalize the sender:
"App Academy <[email protected]>"
.
The mail
method returns an email object (of type
Mail::Message
); it doesn't mail it
though. The caller of UserMailer#welcome_email
will then call
#deliver_now
on the Message
object:
# app/controllers/users_controller.rb
def create
u = User.create(user_params)
msg = UserMailer.welcome_email(u)
msg.deliver_now
render :root
end
Wait. What about the content? Create a file called
welcome_email.html.erb
in app/views/user_mailer/
. This will be the
template used for the email, formatted in HTML:
<!-- app/views/user_mailer/welcome_email.html.erb -->
<!DOCTYPE html>
<html>
<head>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
</head>
<body>
<h1>Welcome to example.com, <%= @user.name %></h1>
<p>
You have successfully signed up to example.com,
your username is: <%= @user.login %>.<br />
</p>
<p>
To login to the site, just follow this link: <%= @url %>.
</p>
<p>Thanks for joining and have a great day!</p>
</body>
</html>
Just like controllers, any instance variables we define in the method become available for use in the views.
It is also a good idea to make a text version for this email. People who don't like HTML emails should have a text version to look at. Also, spam filters will ding you if you don't have a text version; a lot of email gets flagged as spam, so this is a problem.
To do this, create a file called welcome_email.text.erb
in
app/views/user_mailer/
:
<%# app/views/user_mailer/welcome_email.text.erb %>
Welcome to example.com, <%= @user.name %>
===============================================
You have successfully signed up to example.com,
your username is: <%= @user.login %>.
To login to the site, just follow this link: <%= @url %>.
Thanks for joining and have a great day!
When you call the mail
method now, Action Mailer will detect the two
templates (text and HTML) and automatically generate a
multipart/alternative
email; the user's email client will be able to
choose between the two formats.
Sending out an email often takes up to 1sec. This is kind of slow. In particular, if your Rails app is sending a mail as part of a controller action, the user will have to wait an extra second for the HTTP response to be sent.
We'll eventually learn how to "batch up" and send emails offline, but for now just know that if you try to send 100 emails in a controller method, you're going to have trouble responding to requests promptly.
Adding attachments is pretty simple, just add new key/value pairs to the hash-like attachments
variable. This is provided in each instance of ActionMailer::Base
, which comes through ApplicationMailer
.
# app/mailers/user_mailer.rb
def welcome_email
attachments['filename.jpg'] = File.read('/path/to/filename.jpg')
# ...
end
You will probably want to embed URLs into your mailer views just like
for your controller views. Somewhat oddly, you must set an option in
config/environments/production.rb
(and
config/environments/development.rb
for development) so that the
mailer knows the base url of the website:
# app/config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost:3000' }
# app/config/environments/production.rb
config.action_mailer.default_url_options = { host: 'www.production-domain.com' }
You would think that the Rails app knows the hostname (e.g., it
doesn't need you to set this for *_url
methods in controller
views). "Unlike controllers, the mailer instance doesn't have any context about the incoming request so you'll need to provide the :host parameter yourself." (Rails Guides - Action Mailer Basics)
Make sure to (continue to) use the *_url
form of the url helpers,
since when the user opens their email, the email needs to contain the
full hostname of the site to know what host to send the request
to. This is because the email is being opened by a mail-client that
isn't on the same domain as your site (e.g., email is opened on
gmail.com, link needs to point to appacademy.io).
Testing email sending in development can be a PITA. Use the wonderful letter_opener gem. When running the development environment (your local machine), instead of sending an email out to the real world, letter_opener will instead pop open the "sent" email in the browser.
Setup is two lines:
# Gemfile
gem 'letter_opener', group: :development
# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener