The Nylas Threads API gives you more control over how you detect collections of messages, and how you respond to them. This page explains how to work with the Threads API.
How threads work
Section titled “How threads work”Most providers organize messages both as individual messages, and as threads. Threads represent collections of messages that are related to each other and result from participants replying to an email conversation.
Every message is associated with a thread, whether that thread contains only one message or many.
Threads don’t have a metadata object. Metadata lives on the messages in a thread, so filter messages with metadata_pair and group them by thread_id.
When you look at threads from a code perspective, you can detect a thread and group messages using several criteria (the message subject, recipients, senders, and so on). Grouping several messages into a thread provides a coherent view of an email conversation. In long or fast-moving conversations, threads help the reader follow the discussion.
For Google and Microsoft accounts, Nylas returns the provider’s own threads (Gmail threads and Outlook conversations), so they match what users see in their email client. For IMAP accounts, Nylas groups messages into threads itself, and Agent Accounts link replies to their thread using the In-Reply-To and References headers.
Threads are non-linear
Section titled “Threads are non-linear”Threads are organized non-linearly, as collections of related messages. Each thread keeps track of the message_ids for every message in it.
Because all of a thread’s message_ids are logged, you can reply to a specific message by referencing its ID. This creates a new branch of the thread, similar to a tree structure, and the messages in the new branch continue to be associated with the thread.
How replies branch inside one thread
message_ids lists them all, and latest_draft_or_message is the newest one.Diagram as text
- One thread: every message shares the same
thread_id:- Original message (message 1): Starts the thread.
- Reply (message 2): Replies to message 1.
- Reply (message 3): Replies to message 2.
- Reply (message 5): The branch keeps growing.
- Reply (message 4): Also replies to message 2, so it starts a new branch.
- Reply (message 3): Replies to message 2.
- Reply (message 6): Replies to the original again: a second branch.
- Reply (message 2): Replies to message 1.
- Original message (message 1): Starts the thread.
- How a message gets its
thread_id:- Google and Microsoft grants: Nylas returns the provider's own thread: the Gmail thread or the Outlook conversation.
- IMAP grants: Nylas groups the messages and assigns the
thread_id. - Agent Accounts: Nylas links each reply to its thread using the
In-Reply-ToandReferencesheaders. - Start a branch: Send with
reply_to_message_idset to the message you're answering.
Build a fully-fledged inbox with the Threads API
Section titled “Build a fully-fledged inbox with the Threads API”You can use the /threads endpoint to emulate popular inbox UIs, like Gmail or Outlook, which group messages into threads. This gives users a simple, centralized view of their latest email conversation. By combining this view with calls to the /messages endpoint, you can build a fully-fledged inbox in your application.
Before you begin
Section titled “Before you begin”Before you can use the Threads API, you need the following prerequisites:
- A Nylas application.
- A working authentication configuration. Either…
- A Google or Microsoft grant with at least the following scopes:
- Google:
gmail.readonly - Microsoft:
Mail.Read
- Google:
Get a list of threads
Section titled “Get a list of threads”The following examples show how to return the five most recent threads from an authenticated account. For more information on filtering requests, see Avoiding rate limits in Nylas.
curl --compressed --request GET \ --url "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/threads?limit=5" \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json'{ "request_id": "1", "data": [ { "starred": false, "unread": true, "folders": ["CATEGORY_PERSONAL", "INBOX", "UNREAD"], "grant_id": "<NYLAS_GRANT_ID>", "id": "<THREAD_ID>", "object": "thread", "latest_draft_or_message": { "starred": false, "unread": true, "folders": ["UNREAD", "CATEGORY_PERSONAL", "INBOX"], "grant_id": "<NYLAS_GRANT_ID>", "date": 1707836711, "from": [ { "name": "Nyla", } ], "id": "<MESSAGE_ID>", "object": "message", "snippet": "Send Email with Nylas APIs", "subject": "Learn how to Send Email with Nylas APIs", "thread_id": "<THREAD_ID>", "to": [ { } ], "created_at": 1707836711, "body": "Learn how to send emails using the Nylas APIs!" }, "has_attachments": false, "has_drafts": false, "earliest_message_date": 1707836711, "latest_message_received_date": 1707836711, "participants": [ { } ], "snippet": "Send Email with Nylas APIs", "subject": "Learn how to Send Email with Nylas APIs", "message_ids": ["<MESSAGE_ID>"] } ], "next_cursor": "123"}import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});
async function fetchRecentThreads() { try { const identifier = "<NYLAS_GRANT_ID>"; const threads = await nylas.threads.list({ identifier: identifier, queryParams: { limit: 5, }, });
console.log("Recent Threads:", threads); } catch (error) { console.error("Error fetching threads:", error); }}
fetchRecentThreads();from nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"
threads = nylas.threads.list( grant_id, query_params={ "limit": 5 })
print(threads)require 'nylas'
nylas = Nylas::Client.new(api_key: "<NYLAS_API_KEY>")query_params = { limit: 5 }threads, _ = nylas.threads.list(identifier: "<NYLAS_GRANT_ID>", query_params: query_params)
threads.map.with_index { |thread, i| puts("Thread #{i}") participants = thread[:participants]
participants.each{ |participant| puts( "Subject: #{thread[:subject]} | "\ "Participant: #{participant[:name]} | "\ "Email: #{participant[:email]}" ) }}import com.nylas.NylasClient;import com.nylas.models.*;import com.nylas.models.Thread;import java.util.List;
public class ReadThreadParameters { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
ListThreadsQueryParams queryParams = new ListThreadsQueryParams.Builder().limit(5).build(); ListResponse<Thread> threads = nylas.threads().list("<NYLAS_GRANT_ID>", queryParams); int index = 0;
for(Thread thread : threads.getData()){ System.out.printf("%s ", index);
List<EmailName> participants = thread.getParticipants(); assert participants != null;
for(EmailName participant : participants){ System.out.printf(" Subject: %s | Participant: %s | Email: %s%n", thread.getSubject(), participant.getName(), participant.getEmail()); }
index++; } }}import com.nylas.NylasClientimport com.nylas.models.*import java.util.*
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient( apiKey = "<NYLAS_API_KEY>" )
val queryParams = ListThreadsQueryParams(limit = 5) val threads : List<Thread> = nylas.threads().list("<CALENDAR_ID>", queryParams).data
for(i in threads.indices){ print("$i ")
val participants = threads[i].participants
if (participants != null) { for(participant in participants){ println(" Subject: ${threads[i].subject} + " + "Name: ${participant.name} + " + "Email: ${participant.email}") } } }}Return a thread
Section titled “Return a thread”The following request returns a specific thread.
curl --compressed --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/threads/<THREAD_ID>' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --header 'Content-Type: application/json'{ "grant_id": "<NYLAS_GRANT_ID>", "id": "<THREAD_ID>", "object": "thread", "has_attachments": false, "has_drafts": false, "earliest_message_date": 1634149514, "latest_message_received_date": 1634832749, "latest_message_sent_date": 1635174399, "participants": [ { "name": "Renee Smith" }, { "name": "Rebecca Lee Crumpler" } ], "snippet": "jnlnnn --Sent with Nylas", "starred": false, "subject": "Dinner Wednesday?", "unread": false, "message_ids": [ // A list of IDs for all messages in the thread. "<MESSAGE_ID>", "<MESSAGE_ID>" ], "draft_ids": [ // A list of IDs for all drafts in the thread. "<DRAFT_ID>" ], "folders": [ // A list of folders that messages in the thread are associated with. "<FOLDER_ID>", "<FOLDER_ID>" ], "latest_draft_or_message": { "body": "Hello, I just sent a message using Nylas!", "date": 1635355739, "attachments": { "content": "YXR0YWNoDQoNCi0tLS0tLS0tLS0gRm9yd2FyZGVkIG1lc3NhZ2UgL=", "content_type": "text/calendar", "id": "<ATTACHMENT_ID>", "size": 1708, "content_type": "application/ics", "filename": "invite.ics", "id": "<ATTACHMENT_ID>", "size": 1708 }, "folders": { // A list of folders the latest message in the thread is associated with. "<FOLDER_ID>", "<FOLDER_ID>" }, "from": { "name": "Renee Smith", }, "grant_id": "<NYLAS_GRANT_ID>", "id": "<MESSAGE_ID>", // The message ID for the latest message in the thread. "object": "message", "reply_to": { "name": "Renee Smith", }, "snippet": "Hello, I just sent a message using Nylas!", "starred": true, "subject": "Hello From Nylas!", "thread_id": "<THREAD_ID>", "to": { "name": "Geoff Dale", }, "unread": true }}Because threads are non-linear, you can reply to any message listed in the thread’s message_ids by passing its ID as reply_to_message_id when you send. This starts a new branch of the thread, as the thread diagram shows.
Search an inbox for threads
Section titled “Search an inbox for threads”You can add query parameters to a Get all Threads request to search for threads using specific criteria.
You can also use the Nylas SDKs to search for threads, as in the following examples.
import Nylas from "nylas";
const nylas = new Nylas({ apiKey: "<NYLAS_API_KEY>", apiUri: "<NYLAS_API_URI>",});
async function searchInbox() { try { const result = await nylas.threads.list({ identifier: "<NYLAS_GRANT_ID>", queryParams: { search_query_native: "nylas", limit: 5, }, });
console.log("search results:", result); } catch (error) { console.error("Error to complete search:", error); }}
searchInbox();from nylas import Client
nylas = Client( "<NYLAS_API_KEY>", "<NYLAS_API_URI>")
grant_id = "<NYLAS_GRANT_ID>"
messages = nylas.threads.list( grant_id, query_params={ "limit": 5, "search_query_native": 'nylas' })
print(messages)require 'nylas'
nylas = Nylas::Client.new(api_key: '<NYLAS_API_KEY>')query_params = { search_query_native: "subject: hello" }threads, _ = nylas.threads.list(identifier: '<NYLAS_GRANT_ID>', query_params: query_params)
threads.each { | thread | puts thread[:subject]}import com.nylas.NylasClient;import com.nylas.models.*;import java.text.SimpleDateFormat;import com.nylas.models.Thread;import java.util.List;
public class SearchThreads { public static void main(String[] args) throws NylasSdkTimeoutError, NylasApiError { NylasClient nylas = new NylasClient.Builder("<NYLAS_API_KEY>").build();
ListThreadsQueryParams queryParams = new ListThreadsQueryParams.Builder(). searchQueryNative("subject: hello"). limit(5). build();
ListResponse<Thread> thread = nylas.threads().list("<NYLAS_GRANT_ID>", queryParams);
for(Thread email : thread.getData()) { System.out.println(email.getSubject()); } }}import com.nylas.NylasClientimport com.nylas.models.*
fun main(args: Array<String>) { val nylas: NylasClient = NylasClient(apiKey = dotenv["NYLAS_API_KEY"]) val queryParams = ListThreadsQueryParams(limit = 5, searchQueryNative = "subject: hello") val threads : List<com.nylas.models.Thread> = nylas.threads().list("<NYLAS_GRANT_ID>", queryParams).data
for(thread in threads) { println(thread.subject) }}