Looking for emails is straightforward, however, trying to get them all in a single place with less noise can be a difficult task. Thanks to the Nylas Ruby API, we can make email threading possible.
If you want to learn more about Email Threads, read the blog post Stay Threaded: How to Manage and Control Email Threads
Is your system ready to group email threads?
If you already have the Nylas Ruby SDK installed and your Ruby environment is configured, then continue along with the blog.
Otherwise, I would recommend that you read the post How to Send Emails with the Nylas Ruby SDK where the basic setup is explained.
What are we going to talk about?
- What does our application look like?
- Installing the Sinatra package
- Running our Email Threading application
What does our application look like?
Before we jump into the code, let’s see how our application actually works. We will have a single input field accepting the email address to get all the related email threads and messages included in those threads:
We’re going to list all email threads related to the address we used, as long as they have at least two messages:
The email threads are presented in an accordion, and when we open one, we will get the emails in a sequence, with the contact image and with the noise removed. So no emails, phone numbers or reply texts.
As we can see, both simple and nice.
Installing the Sinatra package
To create a Ruby web application, our best option is to use Sinatra, one of the most popular Micro Frameworks in the Ruby world. We might need to install some additional gems:
$ gem install sinatra
$ gem install puma
$ gem install nokogiri
Once installed, we’re ready to go:
First, we’re going to create a folder called EmailThreading, and inside we’re going to create two folders, one called views and other called public.
Let’s create a file called EmailThreading.rb in the EmailThreading folder, and add the following code:
# Import your dependencies
require 'dotenv/load'
require 'nylas'
require 'sinatra'
require 'nokogiri'
require 'date'
# Initialize your Nylas API client
nylas = Nylas::API.new(
app_id: ENV["CLIENT_ID"],
app_secret: ENV["CLIENT_SECRET"],
access_token: ENV["ACCESS_TOKEN"]
)
# Use the Nokogiri gem to clean up the email response
def clean_content(raw_html)
html = raw_html.encode('UTF-8', invalid: :replace, undef: :replace,
replace: '', universal_newline: true).gsub(/\P{ASCII}/,
'')
parser = Nokogiri::HTML(html, nil, Encoding::UTF_8.to_s)
parser.xpath('//script')&.remove
parser.xpath('//style')&.remove
parser.xpath('//text()').map(&:text).join('<br> ')
end
# Get the contact associated to the email address
def get_contact(nylas, email)
contact = nylas.contacts.where(email: email)
if contact[0] != nil
return contact[0]
end
end
# Download the contact picture if it's not stored already
def download_contact_picture(nylas, id)
if id != nil
contact = nylas.contacts.find(id)
picture = contact.picture
file_name = id + ".png"
File.open("public/" + file_name,"wb") do |f|
f.write File.open(picture, 'rb') {|file| file.read }
end
end
end
# When calling the application for the first time
get '/' do
_threads = []
# Call the page
erb :main, :layout => :layout, :locals => {:threads => _threads}
end
# When asking for the email threading
post '/search' do
# Get parameter from form
search = params[:search]
# Search all threads related to the email address
threads = nylas.threads.where(from: search,in: 'inbox')
_threads = []
# Loop through all the threads
threads.each{ |thread|
_thread = []
_messages = []
_pictures = []
_names = []
# Look for threads with more than 1 message
if thread.message_ids.length() > 1
# Get the subject of the first email
_thread.push(thread.subject)
# Loop through all messages contained in the thread
thread.message_ids.each{ |message|
# Get information from the message
message = nylas.messages.find(message)
# Try to get the contact information
contact = get_contact(nylas, message.from[0].email)
if contact != nil and contact != ""
# If the contact is available, downloads its profile picture
download_contact_picture(nylas, contact.id)
end
# Remove extra information from the message, like appended
# message, email and phone number
_messages.push(clean_content(message.body).
gsub(/(\bOn.*\b)(?!.*\1)/,"").
gsub(/[a-z0-9._-]+@[a-z0-9._-]+\.[a-z]{2,3}\b/i,"").
gsub(/(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}/,"").
gsub(/twitter:.+/i,""))
# Convert date to something readable
datetime = Time.at(message.date).to_datetime
date = datetime.to_s.scan(/\d{4}-\d{2}-\d{2}/)
time = datetime.to_s.scan(/\d{2}:\d{2}:\d{2}/)
if contact == nil or contact == ""
_pictures.push("NotFound.png")
_names.push("Not Found" + " on " + date[0] + " at " +
time[0])
else
# If there's a contact, pass picture information,
# name and date and time of message
_pictures.push(contact.id + ".png")
_names.push(contact.given_name + " " + contact.surname +
" on " + date[0] + " at " + time[0])
end
}
_thread.push(_messages)
_thread.push(_pictures)
_thread.push(_names)
_threads.push(_thread)
end
}
# Call the page and display threads
erb :main, :layout => :layout, :locals => {:threads => _threads}
end
Inside the views folder, we need to create two different files, let’s start with layout.erb:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Call the TailwindCSS and Flowbite libraries -->
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://unpkg.com/flowbite@1.5.3/dist/flowbite.min.css" />
<title>Nylas’ Email Threading</title>
<body>
<%= yield %>
<script src="https://unpkg.com/flowbite@1.5.3/dist/flowbite.js"></script>
</body>
</html>
We’re calling both the TailwindCSS and Flowbite libraries to handle CSS.
We need to create then the file main.erb:
<div class="grid bg-green-300 border-green-600 border-b
p-4 m-4 rounded place-items-center">
<p class="text-6xl text-center">Email Threading</p><br>
<!-- Create the form-->
<form method = "post" action="search">
<div class="flex bg-blue-300 border-blue-600 border-b p-4 m-4 rounded place-items-center">
<input type="text" name="search" value="" size="50"></input>
<button type="submit" class="block bg-blue-500 hover:bg-blue-700
text-white text-lg mx-auto py-2 px-4 rounded-full">Search</button>
</div>
</form>
<!-- Do we have any threads? -->
<% if threads != [] %>
<div id="accordion-collapse" data-accordion="collapse">
<!-- Counter to generate accordion elements -->
<% counter = 1 %>
<!-- Loop through each thread -->
<% threads.each do |thread| %>
<!-- Define values for the accordion elements -->
<% heading = "accordion-collapse-heading-" + counter.to_s %>
<% body = "accordion-collapse-body-" + counter.to_s %>
<% _body = "#accordion-collapse-body-" + counter.to_s %>
<h2 id=<%= heading %> >
<button type="button" class="flex items-center justify-between w-full p-5 font-medium
text-left text-gray-500 border border-b-0 border-gray-200
focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800
dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100
dark:hover:bg-gray-800" data-accordion-target=<%= _body %>
aria-expanded="false" aria-controls=<%= body %>>
<!-- Title of the thread -->
<span><%= thread[0] %></span>
<svg data-accordion-icon class="w-6 h-6 shrink-0" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4
4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</button>
</h2>
<div id=<%= body %> class="hidden" aria-labelledby=<%= heading %>>
<div class="p-5 font-light border border-b-0 border-gray-200 dark:border-gray-700">
<!-- Get size of thread array -->
<% count = thread[1].length() %>
<!-- Define amount of elements on the grid -->
<% count_str = "grid-rows-" + count.to_s %>
<div class="grid <%= count_str %> grid-flow-col gap-4">
<!-- Counter to access array elements -->
<% counter = 0 %>
<!-- Loop through each email -->
<% thread[1].each do |message| %>
<div class="col-span-2 ...">
<!-- Display image and date/time of email -->
<img class="mx-auto" src="<%= thread[2][counter] %>"><b>
<p class="text-center"><%= thread[3][counter] %></p></b><br>
<!-- Display the email message -->
<%= message %>
</div>
<% counter = counter + 1 %>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
<% end %>
</div>
If you wonder about the public folder, it will only hold the contact profile picture, so there’s nothing we need to do there.
And that’s it. We’re ready to roll.
Running our Email Threading application
In order to run our application, we just need to type the following on the terminal window:
$ ruby EmailThreading.rb
Our application will be running on port 4567 of localhost, so we just need to open our favourite browser and go to the following address:
http://localhost:4567
If you want to learn more about our Email APIs, please go to our documentation Email API Overview as well Threads and Messages.
Top comments (0)