- Products
- Solutions Use casesBy industry
- Developers
- Resources Connect
- Pricing
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.
Blag aka Alvaro Tejada Galindo is a Senior Developer Advocate at Nylas. He loves learning about programming and sharing knowledge with the community. When he’s not coding, he’s spending time with his wife, daughter and son. He loves Punk Music and reading all sorts of books.