We can use our email client to search through email threads, however, they will come with a lot of extra noise (email responses, long signatures and so on). Gladly, we can use Nylas and Java to build an application to focus on what’s important in our email threads. Getting only the conversations of each thread.
If you already have the Nylas Java SDK installed and your Java environment is configured, then continue along with the blog.
Otherwise, I recommend reading the post How to Send Emails with the Nylas Java SDK where the basic setup is explained.
Before we jump into the code, let’s see how our application actually works. We will have a single input field accepting an email address to get all the related email threads and messages included in those threads:

We will 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 sentence with the contact image and with the noise removed. No emails, phone numbers or reply texts.

As we can see, they are both simple and nice.
Our project is going to be called EmailThreading, and it will have a main class called EmailThreading. We need to change the default pom.xml file and create a Handlebars template called main.
We want to use a web framework, and a very light and fast option is SparkJava.
We’re going to include some useful libraries:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>EmailThreading</groupId>
<artifactId>EmailThreading</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-template-mustache</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>com.nylas.sdk</groupId>
<artifactId>nylas-java-sdk</artifactId>
<version>1.18.0</version>
</dependency>
<dependency>
<!-- jsoup HTML parser library @ https://jsoup.org/ -->
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-template-handlebars</artifactId>
<version>2.7.1</version>
</dependency>
</dependencies>
</project>
Here’s the source code of our EmailThreading.java class:
// Import Java Utilities
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import okhttp3.ResponseBody;
import java.nio.file.Paths;
import java.nio.file.Files;
// Import Spark and Handlebars libraries
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import spark.ModelAndView;
import static spark.Spark.*;
import spark.template.handlebars.HandlebarsTemplateEngine;
//Import Nylas Packages
import com.nylas.*;
import com.nylas.Thread;
//Import DotEnv to handle .env files
import io.github.cdimascio.dotenv.Dotenv;
public class EmailThreading {
static RemoteCollection<Contact> get_contact(Contacts _contact, String email) throws RequestFailedException, IOException {
RemoteCollection<Contact> contact_list = _contact.list(new ContactQuery().email(email));
return contact_list;
}
static void download_contact_picture(NylasAccount account, String id) throws RequestFailedException, IOException{
try (ResponseBody picResponse = account.contacts().downloadProfilePicture(id)) {
Files.copy(picResponse.byteStream(),
Paths.get("src/main/resources/public/images/" + id +".png"));
}catch (Exception e){
System.out.println("Image was already downloaded");
}
}
public static void main(String[] args) {
staticFiles.location("/public");
// Load the .env file
Dotenv dotenv = Dotenv.load();
// Create the client object
NylasClient client = new NylasClient();
// Connect it to Nylas using the Access Token from the .env file
NylasAccount account = client.account(dotenv.get("ACCESS_TOKEN"));
// Get access to messages
Messages messages = account.messages();
// Get access to contacts
Contacts contacts = account.contacts();
// Default path when we load our web application
// Hashmap to send parameters to our handlebars view
Map map = new HashMap();
map.put("search", "");
get("/", (request, response) ->
// Create a model to pass information to the handlebars template
// Call the handlebars template
new ModelAndView(map, "main.hbs"),
new HandlebarsTemplateEngine());
// When we submit the form, we're posting data
post("/", (request, response) -> {
// Get parameter from form
String search = request.queryParams("search");
// Search all threads related to the email address
Threads threads = account.threads();
List<Thread> thread = threads.list(new ThreadQuery().
in("inbox").from(search)).fetchAll();
if (search.equals("")) {
String halt_msg = "<html>\n" +
"<head>\n" +
" <script src=\"https://cdn.tailwindcss.com\"></script>\n" +
" <title>Nylas' Email Threading</title>\n" +
"</head>\n" +
"<body>\n" +
"<div class=\"bg-red-300 border-green-600 border-b
p-4 m-4 rounded w-2/5 grid place-items-center\">\n" +
"<p class=\"font-semibold\">You must specify all fields</p>\n" +
"</div>\n" +
"</body>\n" +
"</html>";
halt(halt_msg);
}
// This ArrayList will hold all the threads with their
// accompanying information
ArrayList<ArrayList<ArrayList<String>>> _threads = new
ArrayList<ArrayList<ArrayList<String>>>();
// Look for threads with more than 1 message
for (Thread msg_thread : thread) {
// Auxiliary variables
ArrayList<ArrayList<String>> _thread = new ArrayList<ArrayList<String>>();
ArrayList<String> _messages = new ArrayList<String>();
ArrayList<String> aux_messages = new ArrayList<String>();
ArrayList<String> _pictures = new ArrayList<String>();
ArrayList<String> _names = new ArrayList<String>();
// Only add threads with two messages or more
if (msg_thread.getMessageIds().size() > 1) {
// Get the subject of the first email
aux_messages.add(msg_thread.getSubject());
_thread.add(aux_messages);
// Loop through all messages contained in the thread
for (String message_ids : msg_thread.getMessageIds()) {
// Get information from the message
Message message = messages.get(message_ids);
// Try to get the contact information
RemoteCollection<Contact> contact = get_contact(contacts,
message.getFrom().get(0).getEmail());
if (contact != null && !contact.fetchAll().get(0).getId().isEmpty()) {
// If the contact is available, downloads its profile picture
download_contact_picture(account, contact.fetchAll().get(0).getId());
}
// Remove extra information from the message, like appended
// message, email and phone number
String parsed_message = message.getBody();
parsed_message = parsed_message.replaceAll("\\\\n", "\n\n");
parsed_message = Jsoup.clean(parsed_message, Safelist.basic());
// Phone number
String pattern_key = "\\d{3}[- .]\\d{3}[- .]\\d{4}";
parsed_message = parsed_message.replaceAll(pattern_key, "");
// Email
pattern_key = "(?i)[A-Z0-9+_.\\-]+@[A-Z0-9.\\-]+";
parsed_message = parsed_message.replaceAll(pattern_key, "");
// Replied message history
pattern_key = "(?s)(\\bOn.*\\b)(?!.*\\1).+";
parsed_message = parsed_message.replaceAll(pattern_key, "");
// Twitter handler
pattern_key = "(?i)<span>twitter:.+";
parsed_message = parsed_message.replaceAll(pattern_key, "");
_messages.add(parsed_message);
// Convert date to something readable
LocalDateTime ldt = LocalDateTime.ofInstant(message.getDate(),
ZoneOffset.UTC);
String date = ldt.getYear() + "-" + ldt.getMonthValue() + "-" +
ldt.getDayOfMonth();
String time = ldt.getHour() + ":" + ldt.getMinute() + ":" + ldt.getSecond();
// If there's no contact
if (contact == null || contact.fetchAll().get(0).getId().isEmpty()) {
_pictures.add("NotFound.png");
_names.add("Not Found" + " on" + date + " at " + time);
} else {
// If there's a contact, pass picture information,
// name and date and time of message
_pictures.add(contact.fetchAll().get(0).getId() + ".png");
_names.add(contact.fetchAll().get(0).getGivenName() + " " +
contact.fetchAll().get(0).getSurname() + " on " +
date + " at " + time);
}
}
// Add ArrayLists to main thread arraylist
// and then add them all
_thread.add(_messages);
_thread.add(_pictures);
_thread.add(_names);
_threads.add(_thread);
}
}
// Hashmap to send parameters to our handlebars view
Map thread_map = new HashMap();
// We're passing the same _threads ArrayList twice
// as we need the copy for processing
thread_map.put("threads", _threads);
thread_map.put("inner_threads", _threads);
// Call the handlebars template
return new ModelAndView(thread_map, "main.hbs");
}, new HandlebarsTemplateEngine());
}
}
Inside the resources folder, we need to create a templates folder and inside we need to create a file called main.hsb:
<html>
<head>
<!-- Call the TailwindCSS and Flowbite libraries -->
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/flowbite.min.css" />
<title>Nylas' Email Threading</title>
</head>
<body>
<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>
<form method="post">
<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>
<div id="accordion-collapse" data-accordion="collapse">
<!-- Loop through each thread -->
{{#each threads}}
<h2 id="accordion-collapse-heading-{{@index}}">
<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="#accordion-collapse-body-{{@index}}"
aria-expanded="false" aria-controls="accordion-collapse-body-{{@index}}">
<!-- Title of the thread -->
<span>{{this.[0].[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="accordion-collapse-body-{{@index}}" class="hidden" aria-labelledby="accordion-collapse-heading-{{@index}}">
<div class="p-5 font-light border border-b-0 border-gray-200 dark:border-gray-700">
<div class="grid "grid-rows-{{this.[1].length}}" grid-flow-col gap-4">
{{#each this.[1]}}
<div class="col-span-2 ...">
{{#with (lookup ../this.[2] @index)}}
<img class="mx-auto" src="images/{{this}}"><b>
{{/with}}
{{#with (lookup ../this.[3] @index)}}
<p class="text-center">{{this}}</p></b><br>
{{/with}}
{{{this}}}
</div>
<br>
{{/each}}
</div>
</div>
</div>
{{/each}}
</div>
</div>
<script src="https://unpkg.com/[email protected]/dist/flowbite.js"></script>
</body>
</html>
To store our images, on the resources folder we need to create a folder called public and inside, a folder called images.
And that’s it. We’re ready to roll.
In order to run our application, we need to compile it by typing the following on the terminal window:
$ mvn package

To actually run the application, we need to type the following:
$ mvn exec:java -Dexec.mainClass="EmailThreading"

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 about our Email APIs, please go to our documentation Email API Overview.