- Products
- Solutions Use casesBy industry
- Developers
- Resources Connect
- Pricing
Getting your emails in a single-view thread makes it easier to get the whole picture in a quick glance. Thanks to the Nylas Python APIs, we can make email threading work for you.
If you already have the Nylas Python SDK installed and your virtual 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 Python SDK where the basic setup is clearly explained.
Before we jump into the code, let’s see how our application works. We will have a single field where we can input an 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 the noise removed. No emails, phone numbers or reply texts.
As we can see, they are both simple and nice.
As we want to create a Python web application, our best option is to use Flask, one of the most popular Micro Frameworks in the Python world. We’re going to add an additional package as well:
$ pip3 install flask $ pip3 install beautifulsoup4
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 templates and other called static.
Let’s create a file called EmailThreading.py in the EmailThreading folder, and add the following code:
# Load your env variables from dotenv import load_dotenv load_dotenv() # Import your dependencies from flask import Flask,render_template,request,redirect,url_for import re import os from nylas import APIClient from bs4 import BeautifulSoup import datetime from datetime import date # Create the app app = Flask(__name__) # Initialize your Nylas API client nylas = APIClient( os.environ.get("CLIENT_ID"), os.environ.get("CLIENT_SECRET"), os.environ.get("ACCESS_TOKEN") ) # Get the contact associated to the email address def get_contact(nylas, email): contact = nylas.contacts.where(email= email) if contact[0] != None: return contact[0] # Download the contact picture if it's not stored already def download_contact_picture(nylas, id): if id != None: contact = nylas.contacts.get(id) picture = contact.get_picture() file_name = "static/" + id + ".png" file_ = open(file_name, 'wb') file_.write(picture.read()) file_.close() # This the landing page @app.route("/", methods=['GET','POST']) def index(): # We're using a GET, display landing page if request.method == 'GET': return render_template('main.html') # Get parameters from form else: search = request.form["search"] # Search all threads related to the email address threads = nylas.threads.where(from_= search, in_= 'inbox') _threads = [] # Loop through all the threads for thread in threads: _thread = [] _messages = [] _pictures = [] _names = [] # Look for threads with more than 1 message if len(thread.message_ids) > 1: # Get the subject of the first email _thread.append(thread["subject"]) # Loop through all messages contained in the thread for message in thread["message_ids"]: # Get information from the message message = nylas.messages.get(message) # Try to get the contact information contact = get_contact(nylas, message["from"][0]["email"]) if contact != None and contact != "": # If the contact is available, downloads its profile picture download_contact_picture(nylas, contact.id) # Remove extra information from the message like appended # message, email and phone number soup = BeautifulSoup(message["body"],features="html.parser") regex = r"(\bOn.*\b)(?!.*\1)" result = re.sub(regex, "", str(soup), 0, re.MULTILINE) regex = r"[a-z0-9._-]+@[a-z0-9._-]+\.[a-z]{2,3}\b" result = re.sub(regex, "", result, 0, re.MULTILINE) regex = r"(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}" result = re.sub(regex, "", result, 0, re.MULTILINE) regex = r"twitter:.+" result = re.sub(regex, "", result, 0, re.MULTILINE) soup = BeautifulSoup(result, "html.parser") for data in soup(['style', 'script']): # Remove tags data.decompose() result = '<br>'.join(soup.stripped_strings) _messages.append(result) # Convert date to something readable date = datetime.datetime.fromtimestamp(message["date"]).strftime('%Y-%m-%d') time = datetime.datetime.fromtimestamp(message["date"]).strftime('%H:%M:%S') if contact == None or contact == "": _pictures.append("NotFound.png") _names.append("Not Found" + " on " + date + " at " + time) else: # If there's a contact, pass picture information, # name and date and time of message _pictures.append(contact["id"] + ".png") _names.append(contact["given_name"] + " " + contact["surname"] + " on " + date + " at " + time) _thread.append(_messages) _thread.append(_pictures) _thread.append(_names) _threads.append(_thread) return render_template('main.html', threads = _threads) # Run our application if __name__ == "__main__": app.run()
Inside the templates folder, we need to create two different files, let’s start with base.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <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/[email protected]/dist/flowbite.min.css" /> <title>Email Threadding</title> </head> <body> {% block content %} {% endblock %} <script src="https://unpkg.com/[email protected]/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.html:
{% extends 'base.html' %} {% block content %} <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="/"> <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? --> <div id="accordion-collapse" data-accordion="collapse"> <!-- Counter to generate accordion elements --> {% set counter = namespace(value=1) %} <!-- Loop through each thread --> {% for thread in threads %} <!-- Define values for the accordion elements --> {% set heading = "accordion-collapse-heading-" + counter.value | string() %} {% set body = "accordion-collapse-body-" + counter.value | string() %} {% set _body = "#accordion-collapse-body-" + counter.value | string() %} <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 --> {% set count = thread[1] | length %} <!-- Define amount of elements on the grid --> {% set count_str = "grid-rows-" + count | string() %} <div class="grid {{ count_str }} grid-flow-col gap-4"> <!-- Counter to access array elements --> {% set _counter = namespace(index=0) %} <!-- Loop through each email --> {% for message in thread[1] %} <div class="col-span-2 ..."> <!-- Display image and date/time of email --> <img class="mx-auto" src="static/{{ thread[2][_counter.index] }}"><b> <p class="text-center">{{ thread[3][_counter.index] }}</p></b><br> <!-- Display the email message --> <p>{{ message | safe }}</p> </div> {% set _counter.index = _counter.index + 1 %} {% endfor %} {% set counter.value = counter.value + 1 %} </div> </div> </div> {% endfor %} <div> {% endblock %} </div>
If you wonder about the static 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.
To run our application, we just need to type the following on the terminal window:
$ python3 EmailThreading.py
Our application will be running on port 5000 of localhost, so we just need to open our favourite browser and go to the following address:
http://localhost:5000
If you want to learn more about our Email APIs, please go to our documentation Email API Overview as well Threads and Messages.
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.