Sending Emails in Rails With ActionMailer

Techie     October 2022

Introduction

Sending emails from a Ruby on Rails app is really easy. You won’t even need a gem to do it! Unless you are too lazy.

According to stats from various research findings (which won’t be cited), email has higher conversion rates than social media and search engine results combined. Hence why in this section, we’ll create a simple mail app that we can use to send cold sales emails (HTML) to multiple prospects at a go and hopefully convert them into clients 🤑️


Part A: Creating a Rails 7 Project

1 . Create a new rails project called emails_example by following this document: Creating A Rails 7 App: With esbuild, bootstrap and jquery.


2 . Install bootstrap icons

$ npm i bootstrap-icons


Part B: Configuring ActionMailer and Gmail

Set up ActionMailer to work with gmail by following this document: Configuring ActionMailer and Gmail.


Part C: Creating the model

1 . Create the prospect.rb model.

i). Generate the model

 $ rails g model prospect
 


ii). Edit the file

# models/prospect.rb

class Prospect < ApplicationRecord

    validates_presence_of :name, :email
  
end


2 . Create migration

i). Edit the migrations

# db/migrate/20221002112836_create_prospects.rb

class CreateProspects < ActiveRecord::Migration[7.0]
  def change
    create_table :prospects do |t|
      t.string :name     
      t.string :email             
      t.integer :email_count       
      t.datetime :last_mailed_at    

      t.timestamps
    end
  end
end


ii) Run migrations

$ rails db:migrate


Part D: Creating the Controllers

1 . Create the prospects controller

i). Generate the prospects controller

 

$ rails g controller prospects


ii). Replace the code in the file with this code

 
# controllers/prospects_controller.rb

class ProspectsController < ApplicationController

  before_action :set_prospect, only: [:show, :edit, :update, :destroy]


  def index
    if params[:search].present?
    @search = params[:search]
    @prospects = Prospect.where("prospects.name like '%#{@search}%'").page(params[:page]).per(500)
    else
    @prospects = Prospect.page(params[:page]).per(500)
    end
  end


  def show
  
  end


  def new
    @prospect = Prospect.new
  end


  def edit
    @prospect = Prospect.find(params[:id])
  end


  def create
    @prospect = Prospect.new(prospect_params)

    respond_to do |format|
      if @prospect.save
        format.html { redirect_to @prospect, notice: 'Prospect was successfully created.' }
        format.json { render action: 'show', status: :created, location: @prospect }
      else
        format.html { render action: 'new' }
        format.json { render json: @prospect.errors, status: :unprocessable_entity }
      end
    end
  end


  def update
    respond_to do |format|
      if @prospect.update(prospect_params)
        format.html { redirect_to @prospect, notice: 'Prospect was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @prospect.errors, status: :unprocessable_entity }
      end
    end
  end


  def destroy
    @prospect.destroy
    respond_to do |format|
      format.html { redirect_to prospects_url, notice: 'Prospect was successfully deleted.'  }
      format.json { head :no_content }
    end
  end


  private
    # Use callbacks to share common setup or constraints between actions.
    def set_prospect
      @prospect = Prospect.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def prospect_params
      params.require(:prospect).permit(:name, :email)
    end
end


2 . Create the emails controller

i). Generate the emails controller

 

$ rails g controller emails


ii). Replace the code in the file with this code

 
# controllers/emails_controller.rb

class EmailsController < ApplicationController

  before_action :set_prospect_ids, only: [:send_to_prospects]

  def index
    @prospects = Prospect.all
  end
  
  
  def send_to_prospects(prospect_ids = @prospect_ids)
  
    if @prospect_ids.present?
    
      @recipients = Prospect.all.where(id: @prospect_ids)
    
      @recipients.each do |recipient|
        MarketingMailer.new_marketing_email(recipient).deliver_later
        recipient.update(email_count: (recipient.email_count + 1), last_mailed_at: Time.now)
      end
    
      render json: {title: "Emails Sent", message: "Successfully sent emails", code: 200 }    

    else
      render json: {title: "Not Sent", message: "Select at least one recipient", code: 400 }
    end
  end


  private
  
    def set_prospect_ids
      @prospect_ids = params[:prospect_ids]
    end
  
end


3 . Edit the routes.rb file to ook like this

# config/routes.rb

Rails.application.routes.draw do
  root "home#index"
  resources :prospects
  resources :emails do
    collection do
      get :send_to_prospects
    end
  end  
end


Part E: Creating the Views

1 . Edit the prospects view templates

i). Create the new.html.erb template and add this code

 
<!-- views/prospects/new.html.erb -->

<div class="container mt-3">
  <div class ="row">
    <h1>New Prospect</h1>

    <%= render 'form' %>


    <div class ="col-1"><%=link_to "Back", prospects_path, class: "btn btn-primary btn-sm btn-info float-right" %></div>

  </div>
</div>


ii). Create the _form.html.erb partial template and add this code

 
<!-- views/prospects/_form.html.erb -->

<div class="row col-lg-6">
  <%= simple_form_for([:main, @prospect]) do |f| %>
    <%= f.error_notification %>

    <div class="form-inputs">
      <%= f.input :name %>
      <%= f.input :email %>
    </div>

    <div class="form-actions">
      <%= f.button :submit, class: "btn btn-success", value: "Create Prospect" %>
    </div>
  <% end %>
</div>


iii). Create the edit.html.erb template and add this code

 
<!-- views/prospects/edit.html.erb -->

<div class="container mt-3"><br><br>
  <div class="row">
  <div><%=link_to "View", @prospect, class: "btn btn-primary btn-sm btn-info float-right" %></div>
    <h1 style="text-align: center">Editing Person</h1>
    
    <%= render "form", prospect: @prospect %>

  </div>
</div>


iv). Create the show.html.erb template and add this code

 
<!-- views/prospects/show.html.erb -->

<div class="col-md-4" style="padding-top: 70px; margin: auto">
  <table class="table">
    <thead>
      <tr>
        <th>Name</th>
        <th>Email</th>
        <th>Email Count</th>
        <th>Last Mailed At</th>                        
      </tr>
    </thead>
    <tbody class="fields">
     <tr>
      <td><%= @prospect.name %></td>
      <td><%= @prospect.email %></td>
      <td><%= @prospect.email_count %></td>
      <td><%= @prospect.last_mailed_at.strftime("%d-%b-%Y at %I:%M%p")  %></td>      
     <tr>
    </tbody>
  </table>
  
  <div class ="container">
   <div class="pull-right">
   <%=link_to " Edit", edit_main_prospect_path, class: "btn btn-warning btn-sm float-left" %>
  </div>
 </div>
</div>


v). Create the index.html.erb template and add this code

 
<!-- views/prospects/index.html.erb -->

<div class="row">
  <h2>List of Prospects</h2>
      <div>
      <%=link_to "New Prospect", new_main_prospect_path, class: "col-3 btn btn-info btn-sm float-right"%>
      </div>
      
      <%= form_tag("/prospects", method: "get") do %>
    <%= label_tag(:range, "Search:") %>
    <%= text_field_tag(:search) %>
  <% end %>

  <%if @search.present?%>
    <h3>Showing results for <%=@search%></h3>
  <%end%>

  <div class="table-responsive">
      <table class="table">
          <thead>
              <tr>
                  <th>#</th>
                  <th>Name</th>
                  <th>Email</th>
                  <th>Email Count</th>
                  <th>Last Mailed At</th>                  
              </tr>
          </thead>
          <tbody>
            <%@prospects.each do |p|%>
              <tr>
                <td><%=p.id%></td>
                <td><%=p.name%></td>
                <td><%=p.email%></td>
                <td><%=p.email_count%></td>
                <td><%=p.last_mailed_at.strftime("%d-%b-%Y at %I:%M%p") if p.last_mailed_at %></td>
                
                <td><%=link_to "View", p, class: "btn btn-primary btn-sm"%></td>
                <td><%=link_to "Edit", edit_prospect_path(p), class: "btn btn-warning btn-sm" %></td>
                <td><%=link_to "Delete", p, class: "btn btn-danger btn-sm", method: :delete, data: { confirm: 'Are you sure?' } %></td>
              </tr>
            <%end%>
          </tbody>
      </table>
  </div>
</div>


vi). Create the index.html.erb template in emails directory and add this code

 
<!-- views/emails/index.html.erb -->

<div class="row">
  <h2 class="text-center">Marketing Email List</h2>
  <a class="col-2 btn btn-info btn-sm bi bi-ui-checks reduce-icon-font float-right" id="select_all"> Select All</a>
  <div class="table-responsive"><br>
      <table class="table">
          <thead>
              <tr>
                  <th></th>              
                  <th>#</th>
                  <th>Name</th>
                  <th>Email</th>
                  <th>Email Count</th>
                  <th>Last Mailed At</th>
              </tr>
          </thead>
          <tbody>
            <%@prospects.each do |p|%>
              <tr class="prospect">
                <td><input type="checkbox" value="<%=p.id%>"></td>
                <td><%=p.id%></td>
                <td><%=p.name%></td>
                <td><%=p.email%></td>
                <td><%=p.email_count%></td>
                <td><%=p.last_mailed_at.strftime("%d-%b-%Y at %I:%M%p") if p.last_mailed_at%></td>
              </tr>
            <%end%>
          </tbody>
      </table>
      <a class="btn btn-warning btn-sm bi bi-send-check-fill reduce-icon-font float-right" id="send_emails"> Send Email Blast</a>
      <a class="btn btn-primary btn-sm bi bi-search reduce-icon-font float-right" id="preview_email"> Preview Email</a>
      
  </div> 
</div>


2 . Install simple_form

i). Add simple_form gem in Gemfile

# Gemfile
# ...
 gem simple_form
 


ii). Bundle up

$ bundle install
 


iii). Generate simple_form helpers

$ rails g simple_form:install
 


Part F: Creating the JavaScript File

1 . Create the email_list.js file in javascript/src directory and add this code

 
// javascript/src/email_list.js

  toggleSelectAllProspects();
  
  $("#send_emails").click(function(){
  
      
    $.get("/emails/send_to_prospects", {prospect_ids: getSelectedRecipients()}, function(result){

      return swal({
        title: result.title,
        text: result.message,
        type: result.code!=200 ? "error" : "success",
        showCancelButton: false,
        animation: "slide-from-top"
      }, function(inputValue) {}); 
         
    });
  });
  
  
  $("#preview_email").click(function(){
    
    if ($.isNumeric(getSelectedRecipients()[0])){
     window.open("/rails/mailers/marketing_mailer/new_prospect_email.html?locale=en&prospect_id=" + getSelectedRecipients()[0])
    }
    
    else{
    
      return swal({
        title: "No Preview",
        text: "Select at least one recipient",
        type: "error",
        showCancelButton: false,
        animation: "slide-from-top"
      }, function(inputValue) {}); 
    
    }   
  });

  
  function toggleSelectAllProspects(){
    var checked_status = true;
    $('#select_all').click(function(){    
      $('.prospect input:checkbox').prop('checked', checked_status);
      checked_status = !checked_status;
    });
  }


  function getSelectedRecipients(){
    var recipientIDs = [];
    
    $('.prospect input:checked').each(function() {
      recipientIDs.push($(this).val());
    });
    return recipientIDs;
  }


2 . Download these files: Sweetalert (JS and CSS) and save them in the javascript/src/vendor and assets/stylesheets/vendor directories respectively.


3 . Import the sweetalert.min.js and email_list.js files in javascript/application.js

 
// javascript/application.js

// ...

import "./src/vendor/sweetalert.min"
import "./src/email_list"


4 . Import the sweetalert.css file in assets/stylesheets/application.bootstrap.scss

 
/* assets/stylesheets/application.bootstrap.scss */

/* ... */

@import "./vendor/sweetalert";


Part G: Creating the Mailer

1 . Create the Marketing Mailer

i). Generate the marketing.rb mailer

 
$ rails generate mailer MarketingMailer


ii). Edit application_mailer.rb in app/mailers

# app/mailers/application_mailer.rb
 
class ApplicationMailer < ActionMailer::Base
  default from: "your_id@gmail.com"
  layout "mailer"
end


iii). Edit marketing_mailer.rb in app/mailers

# app/mailers/marketing_mailer.rb
 
class MarketingMailer < ApplicationMailer

  def new_marketing_email(recipient)
    @recipient = recipient # We can call this variable in the 
                           # new_marketing_email.html.erb view. We can also 
                           # define more variables for the view. 
    mail(to: @recipient.email, subject: 'Write a catchy marketing title here')
  end
  
end


2 . Create the email template in views

i). Create the new_marketing_email.html.erb template in views/marketing_mailer directory and add this code. Notice the name matches the action/method in MarketingMailer.rb file.

 
<!-- views/marketing_mailer/index.html.erb -->

<!doctype html>
<html lang="en-US">
 <head>
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
  <title>Cold Sales Email Template</title>
  <meta name="description" content="New Account Email Template.">
  <style type="text/css">
   a:hover {
    text-decoration: underline !important;
   }
  </style>
 </head>
 <body marginheight="0" topmargin="0" marginwidth="0" style="margin: 0px; background-color: #f2f3f8;" leftmargin="0">
  <!-- 100% body table -->
  <table cellspacing="0" border="0" cellpadding="0" width="100%" bgcolor="#f2f3f8" style="@import url(https://fonts.googleapis.com/css?family=Rubik:300,400,500,700|Open+Sans:300,400,600,700); font-family: 'Open Sans', sans-serif;">
   <tr>
    <td>
     <table style="background-color: #f2f3f8; max-width:670px; margin:0 auto;" width="100%" border="0" align="center" cellpadding="0" cellspacing="0">
      <tr>
       <td style="height:80px;">&nbsp;</td>
      </tr>
      <tr>
       <td style="text-align:center;">
        <a href="https://techietuts.com" title="logo" target="_blank">
         <img style="border-radius: 20px;" width="300" src="https://techietuts.com/assets/img/logo_01.png" title="logo" alt="logo">
        </a>
       </td>
      </tr>
      <tr>
       <td style="height:20px;">&nbsp;</td>
      </tr>
      <tr>
       <td>
        <table width="95%" border="0" align="center" cellpadding="0" cellspacing="0" style="max-width:670px; background:#fff; border-radius:3px; text-align:left;-webkit-box-shadow:0 6px 18px 0 rgba(0,0,0,.06);-moz-box-shadow:0 6px 18px 0 rgba(0,0,0,.06);box-shadow:0 6px 18px 0 rgba(0,0,0,.06);">
         <tr>
          <td style="height:40px;">&nbsp;</td>
         </tr>
         <tr>
          <td style="padding:0 35px;">
           <h1 style="color:#1e1e2d; font-weight:500; margin:0;font-size:32px;font-family:'Rubik',sans-serif;">Hi <%= @recipient.name %>, <br>
            <br>
           </h1>
           <p style="font-size:15px; color:#455056; margin:8px 0 0; line-height:24px;"> My name is [my name] and I head up business development efforts with [my company]. We recently launched a new platform that [one-sentence pitch]. <br>
            <br> I’d like to speak with someone from <%= @recipient.name %> who is responsible for [handling something that's relevant to my product]. <br>
            <br> I am taking an educated stab in the dark here, however, based on your online profile, you may be the right person to connect with or can at least point me in the right direction. <br>
            <br> If that’s you, are you open to a fifteen-minute call on [time and date] to discuss ways the [company name] platform can specifically help your business? If not you, can you please put me in touch with the right person? <br>
            <br> I appreciate the help! <br>
            <br> Best, <br>
            <br> [your name]
          </td>
         </tr>
         <tr>
          <td style="height:40px;">&nbsp;</td>
         </tr>
        </table>
       </td>
      </tr>
      <tr>
       <td style="height:20px;">&nbsp;</td>
      </tr>
      <tr>
       <td style="text-align:center;">
        <p style="font-size:14px; color:rgba(69, 80, 86, 0.7411764705882353); line-height:18px; margin:0 0 0;">&copy; <strong>www.techietuts.com</strong>
        </p>
       </td>
      </tr>
      <tr>
       <td style="height:80px;">&nbsp;</td>
      </tr>
     </table>
    </td>
   </tr>
  </table>
  <!--/100% body table-->
 </body>
</html>

NB: As a best practice, you can create a text version of the email in case the receiver doesn’t use HTML email. This goes in the same folder and has the same file name but uses the text.erb extension instead of html.erb. But for this tutorial, we will rely on the HTML email.


3 . Set up the email preview

i). Open the marketing_mailer_preview.rb file located in tests/mailer/previews and replace the code with this:

 
# tests/mailer/previews/marketing_mailer_preview.rb
# Preview all emails at http://localhost:3000/rails/mailers/marketing_mailer

class MarketingMailerPreview < ActionMailer::Preview

  def new_prospect_email
    @recipient = Prospect.find(params[:prospect_id])
    
    MarketingMailer.new_marketing_email(@recipient)  
  end


end


Final Results

Navigate to http://localhost:3000/prospects/new and create new prospects. Then to http://localhost:3000/emails to send the emails.

missing image


missing image


Thanks for reading, see you in the next one!