Logging emails with Interceptors

by Brian Rossetti on November 28, 2016

This post will present one implementation sneding user generated emails which will then be logged to the database before they are sent out from the application. In order to do this we will be using a particular method hook of ActionMailer called Interceptors, which allow us to make modifications to mailers before they go out.

lets start by creating the user generated email, first lets create the mailer:

in `app/mailers/some_mailer.rb

class SomeMailer < ActionMailer::Base
  def prepare(message)
    @body = message.body

    mail(from: message.from, to: message.to, subject: message.subject)
  end
end

in app/views/some_mailer/prepare.html.haml

%p=Inquiry:
%p=@body

%p You can accept this invitation by logging in and viewing your dashboard

%p= link_to "Login", new_user_session_url

This is probably the simplest implementation of a mailer you can implement.

next, lets create a model to log our email in:

in app/models/email_message.rb

# == Schema Information
#
# Table name: email_message
#
#  id      :integer          not null, primary key
#  to      :string(255)
#  from    :string(255)
#  subject :string(255)
#  body    :text
#

class EmailMessage < ApplicationRecord
  validates :to, :from, :subject, :body, presence: true
end

The model contains some basic validations that will be used in the controller before we send out any email.

now lets add the route, controller, and view for sending out this user generated emailer.

in config/routes.rb

Rails.application.routes.draw do
resources :messages, only: [:show] do
member do
post "example"
end
end
end

we first need a route that we will use as an example of creating a custom endpoint for posting the email data for processing by ActionMailer.

in app/controllers/messages_controller.rb

class MessagesController < ApplicationController

  def example
    @message = EmailMessage.new(message_params)
    if @message.valid?
      ::SomeMailer.prepare(@message).deliver!
      flash[:notice] = "Your Message has been sent"
    else
      flash[:alert] = @message.errors.full_messages.join(", ")
    end
    redirect_to root_path
  end

  private

  def message_params
    params.require(:email_message).permit(:to, :from, :subject, :body)
  end
end

next we add the controller and define the custom checks in the endpoint. In this example we are just checking that the message is valid?. Notice how we are not actually saving anything to the database in the controller. if the message is valid, we just deliver the email.

in app/views/messages/example.html.haml

.modal.fade{"aria-hidden" => "true", "aria-labelledby" => "composeMailLabel", :role => "dialog", :tabindex => "-1"}
  .modal-dialog{:role => "document"}
    .modal-content
      .modal-header
        %button.close{"aria-label" => "Close", "data-dismiss" => "modal", :type => "button"}
          %span{"aria-hidden" => "true"} &#215;
        %h4#myModalLabel.modal-title New Message
          =simple_form_for EmailMessage.new(), url: example_message_path, class: "form-horizontal" do |f|
            .modal-body
              .row.form-row
                .pull-left
                  To:
              .col-xs-10.pull-right
                =f.input :to, label: false, input_html: {class: "form-control"}
            .row.form-row
              .pull-left
                From:
              .col-xs-10.pull-right
                =f.input :from, label: false, input_html: {class: "form-control"}, input_html: {value: current_user.email}
            .row.form-row
              .pull-left
                Subject
              .col-xs-10.pull-right
                =f.input :subject, label: false, input_html: {class: "form-control"}
            .row.form-row
              .col-xs-12
                =f.input :body, label: false, input_html: {class: "form-control", style: "min-height: 250px;"}
            .modal-footer
              =f.submit "Send Email", class: "btn btn-primary"

In the view we are using simple_form with an empty EmailMessage. Because we are sending this throguh a form, rails will default to a POST request to the example endpoint.

Once you have the basic infrastructure up for sending you message then you can implement your Interceptor for logging purposes.

in app/models/mail_logger.rb

class MailLogger

  def self.delivering_email(message)
    @to      = message.to.to_s
    @from    = message.from.to_s
    @subject = message.subject.to_s
    @message = message.body.to_s
    EmailMessage.create!(to: @to, from: @from, subject: @subject, body: @message)
  end

end

the method self.delivering_email(message) is the hook method used to tell ActionMailer to do whatever this method defines before sending an email out of the application. After creating a Logging class, we now have to make sure we insert it into ActionMailler so the hook gets picked up. Lets use an initializer here.

in config/initializers/mail_logging.rb

ActionMailer::Base.register_interceptor(MailLogger)

And thats it, now a user can create an email inside the app, and on top of that, any email that leaves your application is logged in the database.

Some other examples of Interceptors can be seen in the following articles:

multiple Interceptor Examples

filtering certain mailers

Email Interceptor for Development

Interceptors for testing multiple mail providers

emails on Acid - multiple profivers

Ruby On Rails

Let's Get In Touch!


Our best work gets done when we can work face-to-face with you.

770-317-4866