Skip to content

Backend Training: Mandrill Mailer

Nelson Lee edited this page Dec 12, 2017 · 3 revisions

🔖 mandrill-api, letter_opener

In this section, we will go over on how to setup mailers to send out emails when a user subscribes or submits the contact us form.

Install Gem

First, let's include the mandrill-api gem in our Gemfile.

# Gemfile
.....

# Mailchimp Mailer
gem 'mandrill-api'

group :development, :test do
  .....
end

.....

Install the gem

$ bundle install

Setup Development Environment

For development, we would like to user letter_opener to open the mail instead of receiving real emails.

# config/environments/development.rb
Rails.application.configure do
  ......

  # Don't care if the mailer can't send.
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

  config.action_mailer.raise_delivery_errors = false

  config.action_mailer.perform_caching = false

  config.action_mailer.delivery_method = :letter_opener

  ......
end

Setup Mandrill Mailer

Next, create a base mandrill mailer file inside app/mailers/

# app/mailers/mandrill_mailer.rb
require 'mandrill'

# Base Mandrill API Connection
class MandrillMailer < ApplicationMailer

  include Rails.application.routes.url_helpers
  include ActionView::Helpers::UrlHelper

  private

  def send_mail(email, subject, body)
    mail(to: email, subject: subject, body: body, content_type: 'text/html')
  end

  def mandrill_template(template_name, attributes)
    mandrill = Mandrill::API.new(ENV['MANDRILL_SMTP_API_KEY'])

    merge_vars =
      attributes.map do |key, value|
        { name: key, content: value }
      end

    mandrill.templates.render(template_name, [], merge_vars)['html']
  end

end

And update the application_mailer with some basic configs

# app/mailers/application_mailer.rb
# Application Mailer
class ApplicationMailer < ActionMailer::Base

  layout 'mailer'

  default(
    from: ENV['SMTP_NOTIFICATION_EMAIL'],
    reply_to: ENV['SMTP_NOTIFICATION_EMAIL']
  )

end

Great! We have our base mailer setup now let's create a mailer for the contact form.

# Contact Mailer
class ContactMailer < MandrillMailer

  def notify_admin(contact)
    @contact = contact.reload
    subject = 'Contact Submission'
    body = mandrill_template(
      ENV['MANDRILL_CONTACT_SUBMISSION_TEMPLATE'],
      'TABLE' => table
    )
    email_to = ENV['MANDRILL_SMTP_NOTIFICATION_EMAIL']
    send_mail(email_to, subject, body)
  end

  private

  def table
    render_to_string(
      partial: 'mailers/table',
      locals: {
        resource: @contact,
        fields:   %w[name email message created_at]
      },
      layout: false
    )
  end

end

Setup Environment Variables

Next, let's setup the environment variables...

# .env
SMTP_NOTIFICATION_EMAIL='[email protected]'
MANDRILL_SMTP_API_KEY='vLGYnebipCsaycGSTAfGew'
MANDRILL_SMTP_USER_NAME='[email protected]'
MANDRILL_SMTP_DOMAIN='cleverbanana.com'
MANDRILL_SMTP_NOTIFICATION_EMAIL='[email protected]'
MANDRILL_CONTACT_SUBMISSION_TEMPLATE='cb_web-general'

Setup Views

Now, let's add a generic view for our emails.

# app/views/mailers/_body.slim
table[style="width: 100%; border-collapse: collapse; border: 1px solid #eee; text-align: left;"]
  thead
    tr[style="background: #ffb600; color: white;"]
      th[style="white-space:nowrap; padding: 10px 15px;"] FIELD
      th[style="padding: 10px 15px;"] VALUE
  tbody
    - fields.each do |field|
      tr[style="border-bottom: 1px solid #eee;"]
        td[style="white-space:nowrap; padding: 10px 15px;"]= field.humanize
        td[style="padding: 10px 15px;"]= resource.send(field.to_sym)

Hook it up with Model

Now, the mailer is ready, let's hook it up with the contact model. We want to receive mail only if the contact is created successfully.

# app/models/contact.rb
class Contact < ApplicationRecord

  after_create :notify_admin

  ......

  private

  def notify_admin
    ContactMailer.notify_admin(self).deliver_now
  end

end

Now, submit the contact form and you should see... contact#mail

Setup Subscribers

Great! it worked. Now let's add mailer for the subscriber form as well.

# app/mailers/subscriber_mailer.rb
# Subscriber Mailer
class SubscriberMailer < MandrillMailer

  def notify_admin(subscriber)
    @subscriber = subscriber
    subject = 'New Subscriber'
    body = mandrill_template(
      ENV['MANDRILL_CONTACT_SUBMISSION_TEMPLATE'],
      'TABLE' => table
    )
    email_to = ENV['MANDRILL_SMTP_NOTIFICATION_EMAIL']
    send_mail(email_to, subject, body)
  end

  private

  def table
    render_to_string(
      partial: 'mailers/table',
      locals: {
        resource: @subscriber,
        fields:   %w[email]
      },
      layout: false
    )
  end

end

p.s. here we have removed the created_at because subscriber is not stored in the database and hence don't have a created_at date.

# app/models/subscriber.rb
class Subscriber

  .....

  def create
    return false unless valid?
    notify_admin
  end

  .....

  private

  def notify_admin
    SubscriberMailer.notify_admin(self).deliver_now
  end

  .....

end

Now, go to your subscriber form and submit, and you should see... subscriber#mail

Add Specs

Now, let's add specs to validate these mailers are called

# spec/models/subscriber_spec.rb
require 'rails_helper'

RSpec.describe Subscriber, type: :model do
  ......

  describe 'Callbacks' do
    let(:subject) { build(:subscriber) }

    it 'should send mail' do
      expect(subject).to receive(:notify_admin)
      subject.save
    end
  end
end

and

# spec/models/contact_spec.rb
require 'rails_helper'

RSpec.describe Contact, type: :model do
  ......

  describe 'Callbacks' do
    let(:subject) { build(:contact) }

    it 'should send mail to admin' do
      expect(subject).to receive(:notify_admin)
      subject.save
    end
  end
end

Now, run the specs.

$ rspec spec
.............................**...........*.....................................

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) ContactDecorator add some examples to (or delete) /Users/ilunglee/Work/rails/mock_project/spec/decorators/contact_decorator_spec.rb
     # Not yet implemented
     # ./spec/decorators/contact_decorator_spec.rb:4

  2) FaqDecorator add some examples to (or delete) /Users/ilunglee/Work/rails/mock_project/spec/decorators/faq_decorator_spec.rb
     # Not yet implemented
     # ./spec/decorators/faq_decorator_spec.rb:4

  3) AdminUser add some examples to (or delete) /Users/ilunglee/Work/rails/mock_project/spec/models/admin_user_spec.rb
     # Not yet implemented
     # ./spec/models/admin_user_spec.rb:4


Finished in 5.16 seconds (files took 5.96 seconds to load)
80 examples, 0 failures, 3 pending

Great! It passes. Let's commit and move on.

$ git add -A
$ git commit -m 'Mandrill Mailer'

↪️ Next Section: ActiveAdmin