Skip to content
PLAY VIDEO PLAY VIDEO PLAY VIDEO
By Sameera Gayan

Receiving and processing inbound emails with Action Mailbox and Rails 6

The vast majority of web applications these days have some sort of a notification system to communicate with their users. Typically, this is by sending them emails. Given emails are a major part of modern-day communication, application developers try to utilise email workflows for users to interact with their apps.

One example would be: when a user receives an email from an app (e.g issue tracker), they can then reply to that email with a comment and that comment will automatically be added to the app database. This makes the user experience seamless as users now don't have to switch between apps to provide data. In this case, the email client and a ticketing app.

Take a project management application as an example. Imagine a workflow of creating a task and assigning it to the team. Each member will receive an email and they can reply to the email with a status update. Allowing them to reply to the same email with their status update will make their work easier compared to them having to log in to a separate project management app and updating the status there.

From a project management aspect, the above task should have all the status updates listed under the task, so it is easy to read and understand by anyone who logs in to the system. All the data in one place.

For Rails, historically there was no straight-forward way to handle incoming emails. To achieve something like this, you would have had to either use a gem like griddler or set up your own webhooks from a provider like cloudmailin.

But with new Rails 6 release, receiving emails has never been easier. Rails 6 has introduced Action Mailbox, a built-in mechanism to receive emails.

In this post, let's have a look at how we can set up Action Mailbox to receive emails in Rails 6.

Using the above project management example, we will create a small project management app. In this example, users of this app should be able to:

  1. Create tasks.
  2. Receive an email about the tasks they have been assigned.
  3. Reply to that email with their status update, with the data from the status update listed in the app under the given task.

Disclaimer: since we are focusing on Action Mailbox functionality, I will skip discussing points 1 and 2 in this blog. There are many tutorials out there on how to set up sending emails with Rails. Also, we'll skip user creation and authentication.

Now, let's get into the fun part, coding...

Setting up the rails project

First things first, let's create a Rails 6 project, Let's call it, Projectmanager.

1 - Make sure you have Rails 6 installed on your machine.

# Checking the Rails version in the command line.
$ rails -v #=> Rails 6.0.0

2 - We'll create a Rails 6 project and skip mini-test and later add rspec.

$ rails new Projectmanager -T

3 - Now we'll create a simple Task object. For simplicity, Task will only have one field called 'name'.

$ rails generate model Task name:string

4 - Next up is the comments model for tasks. comments will have only one field called note, and an association to a task.

$ rails generate model Comment task:references note:text

5 - Now to set up the Active Record associations. You will see that the Comment model already has belongs_to but we need to add has_many to Task.

# app/models/task.rb
class Task < ApplicationRecord
  has_many :comments
end

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :task
end

6 - Don't forget to migrate the database.

$ rails db:migrate

7 - Now, just to make sure that we have some data, let's create a Task and a comment for it in the Rails console.

task = Task.create(name: 'My Awesome task')
comment = Comment.create(task: task, note: 'This is the first update')

8 - Now let's create a controller with one action (show) to list the task and its comments.

$ rails generate controller tasks show

9 - And to set the root url, change get 'tasks/show' to root 'tasks#show'.

# config/routes.rb
Rails.application.routes.draw do
  root 'tasks#show'
end

10 - Now set up the show method to get the first task and its comments.

# app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  def show
    @task = Task.includes(:comments).first
  end
end

11 - Let's update the generated view to show the comments and the task. Please note, we are just adding basic HTML and not focusing on styling up the page here.

# app/views/tasks/show.html.erb
<h1><%= @task.name %></h1>
<ul>
  <% @task.comments.find_each do |comment| %>
    <li><%= comment.note %></li>
  <% end %>
</ul>

Now comes the fun part. Since we have set up the basic Rails project, let's dive into the actual focus of this blog post. Adding Action Mailbox.

Adding Action Mailbox

Let's quickly go through the basic concepts of Action Mailbox.

All the inbound emails received by Action Mailbox are converted to an InboundEmail record using Active Record. So, the ApplicationMailbox class acts as a base class just like ApplicationRecord does for models.

This instance of the InboundEmail record is stored in the actionmailboxinbound_emails table, hence the *_create_action_mailbox_tables.action_mailbox.rb migration.

If you open up the below *_create_action_mailbox_tables.action_mailbox.rb migration, you'll notice that it has a few columns, such as status:

# https://github.com/rails/rails/blob/master/actionmailbox/app/models/action_mailbox/inbound_email.rb#L9
- Pending: Just received by one of the ingress controllers and scheduled for routing.
- Processing: During active processing, while a specific mailbox is running its #process method.
- Delivered: Successfully processed by the specific mailbox.
- Failed: An exception was raised during the specific mailbox's execution of the +#process+ method.
- Bounced: Rejected processing by the specific mailbox and bounced to sender.

Also, it keeps the message id and the message_checksum to filter out duplicate messages. Another migration *_create_active_storage_tables.active_storage is generated to keep the email details. This is done via rails ActiveStorage.

One more thing to note is that by default, once a mail is processed, it will be queued for incineration. This is to make sure that the application is not holding any sensitive data within the emails. By default, the cleanup will happen after 30 days. However, if you want to change it, you can do it via config.action_mailbox.incinerate_after in the application configuration.

Okay, after that basic understanding on how Action Mailbox works, let's continue with our code.

1 - To start, we need to add action_mailbox to our project.

$ rails action_mailbox:install

You'll see the following in the command line:

Copying application_mailbox.rb to app/mailboxes
      create  app/mailboxes/application_mailbox.rb
Copied migration 20190906012037_create_active_storage_tables.active_storage.rb from active_storage
Copied migration 20190906012038_create_action_mailbox_tables.action_mailbox.rb from action_mailbox

2 - Next, let's create the tables.

$ rails db:migrate

For this example, let's assume we are receiving the comments for the tasks in the following format.

- Address: replies@projectmanager.com
- Subject: comment-<taskid>
- Body: Comment text for the task

3 - Now, you need to set up something called the mailbox route. Think of this as a normal route, but for inbound emails. Open ApplicationMailbox and add the following.

class ApplicationMailbox < ActionMailbox::Base
  routing :all => :comments
end

Note that we can use a regular expression for the routing, meaning you can easily handle multiple types of inbound emails, if needed.

The second part is => :comments. This is where we tell Action Mailbox which mailbox to route to. In this case, we are routing all the inbound email to the comments mailbox. But ideally, as above, you would want to direct these using a regular expression.

4 - Now let's create the comments mailbox.

$ rails generate mailbox comments

Note: Your mailbox name should match the name you've given it in the routing params. In our example, our routing param is:

routing :all => :comments

So, our mailbox class is CommentsMailbox.

The reason behind this is, "#{mailbox_name.to_s.camelize}Mailbox".constantize is how Action Mailbox finds the mailbox class, see the source code if you are interested.

5 - Once that is done, let's open up the CommentsMailbox class.

class CommentsMailbox < ApplicationMailbox
  def process
  end
end

Here, the process method is the one we are interested in. This is where all the processing happens and we write our own logic to handle the inbound emails.

6 - Now, before going any further, let's have a look at what we have available to us within the process method. To do so, let's add a breakpoint to the process method.

class CommentsMailbox < ApplicationMailbox
  def process
    byebug
  end
end

One cool thing about Action Mailbox is that it ships with a UI to test inbound emails in the development environment. To access this, fire up the Rails server

$ rails server

And go to <your host>/rails/conductor/action_mailbox/inbound_emails and click on Deliver new inbound email. Fill in all the details as per the above email format (e.g subject to comment-<taskid>) and then click send.

email_details

Since we have a breakpoint at the process method, now the execution of the server process should stop at the process method.

7 - At this point, we have two main objects to work with

1 - inbound_email

The current inbound email record as an InboundEmail instance, for example:

#<ActionMailbox::InboundEmail id: 1, status: "processing", message_id: "5d71ef414d844_7a33ff4d312d9e885c0@192-168-1-8.tpgi...",
  message_checksum: "c2e141423e6733925ed8ce8581beb28cde973552", created_at: "2019-09-06 05:31:45", updated_at: "2019-09-06 05:31:48">

2 - Mail object

This is probably the most important one. This is a Ruby Mail object. Since it is a standard Ruby Mail object, we can now perform actions like getting the subject, finding the sender, etc. Since we know the subject has the task ID, we can split the subject, find the task and update the comment from the email body.

These steps can vary based on how you want to process the inbound email and required validations such as "is the sender email is already a registered email?", etc.

However, for the scope of this blog post, I'll keep it simple and just find the Task by the ID. Since this is just simple Ruby/Rails code, I'm not going to explain it in detail. The following is the final code:

# app/mailboxes/comments_mailbox.rb
class CommentsMailbox < ApplicationMailbox
  def process
    task = find_task(mail.subject)
    update_comments(task, mail.decoded)
  rescue
    # Proper error handler here.
  end

  private

  def update_comments(task, comment_string)
    task.comments.create!(note: comment_string)
  end

  def find_task(subject)
    task_id = subject.split("-").last.to_i
    Task.find(task_id)
  end
end

Now when you submit the form, you should see the comment is added from the inbound email.

via_inbound_email

8 - Cool, almost done. To wrap things up, let's add a test.

If you're following this tutorial, you might remember that we opted-out of the default Minitest library by passing -T when we created the project. That is because we want to add RSpec. If you are familiar with Minitest over RSpec, you're more than welcome to go with Minitest.

To add RSpec, open your Gemfile and add rspec-rails to your :development, :test group block.

# Gemfile
group :development, :test do
  ...
  gem 'rspec-rails', '~> 3.8'
end

And then install rspec-rails.

$ bundle install
$ rails generate rspec:install

ActionMailbox comes with a test helper ActionMailbox::TestHelper with some helper methods for easy testing. At the time of this blog post, RSpec doesn't have a way of accessing it out-of-the-box, but adding it to RSpec is easy. You can add it as a support helper by creating a file in spec/support/action_mailbox.rb.

# spec/support/action_mailbox.rb

require 'action_mailbox/test_helper'

RSpec.configure do |config|
  config.include ActionMailbox::TestHelper
end

9 - Don't forget to uncomment this line from the rails_helper, if it isn't already.

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

With this, we can access helper methods such as createinboundemailfrommail.

10 - Let's write a quick spec. It's pretty self-explanatory if you are familiar with testing, so I will not explain the spec; it simply checks if the comment from an inbound email is being added to the task.

# spec/mailboxes/comments_mailbox_spec.rb
require 'rails_helper'

describe CommentsMailbox do
  let(:task) { Task.create!(name: "Test Task") }

  subject do
    receive_inbound_email_from_mail(
      from: 'test@user.com',
      to: 'comments@taskmanager.com',
      subject: email_subject,
      body: "Body text"
    )
  end

  context 'email subject with a correct task ID' do
    let(:email_subject) { "comment-#{task.id}" }

    it 'adds a comment to the task' do
      expect { subject }.to change(task.comments, :count).by(1)
    end
  end

  context 'email subject with an invalid task ID' do
    let(:email_subject) { "comment-#{task.id}1" }

    it 'does not add a comment to the task' do
      expect { subject }.to change(task.comments, :count).by(0)
    end
  end
end

That's it. Now when you run the spec, you should see that it's all working.

$ rspec # => 2 examples, 0 failures

Deploying to production

Clearly, everything that we have done above is for the local environment. But once you get everything working locally, deploying it to production is just a matter of setting up the relevant configurations with your inbound email provider such as Mailgun.

You can read the official documentation on setting up Action Mailbox with an inbound email provider here.

Happy coding...

Latest Articles by Our Team

Our expert team of designers and developers love what the do and enjoy sharing their knowledge with the world.

We Hire Only the Best

reinteractive is Australia’s largest dedicated Ruby on Rails development company. We don’t cut corners and we know what we are doing.

We are an organisation made up of amazing individuals and we take pride in our team. We are 100% remote work enabling us to choose the best talent no matter which part of the country they live in. reinteractive is dedicated to making it a great place for any developer to work.

Free Community Workshops

We created the Ruby on Rails InstallFest and Ruby on Rails Development Hub to help introduce new people to software development and to help existing developers hone their skills. These workshops provide invaluable mentorship to train developers, addressing key skills shortages in the industry. Software development is a great career choice for all ages and these events help you get started and skilled up.

  • Webinars

    Webinars

    Webinars are our online portal for tips, tricks and lessons learned in everything we do. Make the most of this free resource to help you become a better developer.

    Learn more about webinars

  • Installfest

    Installfest

    The Ruby on Rails Installfest includes a full setup of your development environment and step-by-step instructions on how to build your first app hosted on Heroku. Over 1,800 attendees to date and counting.

    Learn more about Installfest

  • Development Hub

    Development Hub

    The Ruby on Rails Development Hub is a monthly event where you will get the chance to spend time with our team and others in the community to improve and hone your Ruby on Rails skills.

    Learn more about Development Hub

Get the “reinteractive Review” Monthly Email