Skip to content

Latest commit

 

History

History
224 lines (167 loc) · 6.96 KB

mailing-1.md

File metadata and controls

224 lines (167 loc) · 6.96 KB

Action Mailer Basics

Action Mailer allows you to send emails from your application. There are two parts:

  • A mailer class extending ActionMailer::Base. This works similarly to our ApplicationController extending ActionController::Base. Mailers live in app/mailers.
  • Views, which live in app/views/[mailer_name].

Your first mailer

Generate the Mailer

$ 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.

Edit The Mailer

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

Create A Mailer View

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.

Mailing Is Slow

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.

Mailing Details

Adding Attachments

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

Generating URLs In Action Mailer Views

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).

Letter Opener

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

Resources