Nobody thinks about their inbox as a pile of individual emails. They think in conversations: the back-and-forth about Wednesday’s dinner, the thread with a client about an invoice. A thread is that conversation — the group of related messages that belong together — and treating it as one object is what makes an inbox feel like an inbox instead of a flat list of disconnected emails. Nylas exposes threads as a first-class resource, so you can list, read, and organize a whole conversation in a single call. This post builds the conversation view with the Email API and the CLI.
It’s a worked use case rather than an endpoint tour, covering threads from two angles: the HTTP API your backend calls and the nylas CLI for browsing conversations from the terminal.
A thread is a conversation, not a message
A thread groups the messages that form one conversation, and the link between them is the thread’s message_ids array — the list of every message that belongs to it. Where a message is a single email, a thread is the whole exchange, so the subject, the participants, and the read state describe the conversation as a unit rather than any one reply in it. That’s the mental model the API is built around.
The thread object gives you the conversation’s summary without making you fetch every message in it. A single thread carries the subject, a snippet of the latest content, the participants across the conversation, has_attachments and has_drafts flags, an unread and starred state for the whole thread, and three timestamps: earliest_message_date, latest_message_received_date, and latest_message_sent_date. It also includes latest_draft_or_message — the full most-recent message inline — so a conversation list can show the latest activity without a second request.
Threads versus messages: which one to use
The distinction that matters for your code is that threads are for reading and organizing, while messages are for sending. You list threads to render a conversation view, you read a thread to see the exchange, and you organize a thread to act on the whole conversation, but you never create a thread directly. There’s no “create thread” endpoint, because a thread isn’t something you make; it forms automatically as messages reply to each other.
The division of labor is clean. Display and organization happen at the thread level: the inbox list, the unread badges, the archive action. Composing happens at the message level: you send and reply with the messages endpoint, and the provider threads your reply into the right conversation based on its headers. When you want to show a conversation, reach for threads; when you want to send into one, reach for messages. Mixing that up — trying to “send a thread” — is the most common conceptual stumble with this part of the API.
List conversations
Rendering a conversation view is a GET /v3/grants/{grant_id}/threads request, which returns threads with all their summary fields, enough to build an inbox list without touching the messages endpoint. Each thread comes back with its subject, snippet, participants, unread and starred state, and the inline latest_draft_or_message, so one call populates a whole screen of conversations.
curl --request GET \
--url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/threads?limit=20&unread=true" \
--header "Authorization: Bearer <NYLAS_API_KEY>"
The same standard filters that work on messages work here: unread, starred, subject, from, in for a folder, and the date filters, so you can list every unread conversation in the inbox. The thread-specific date filters are worth knowing too: latest_message_after and latest_message_before filter by when the conversation last saw activity, which is exactly how you’d sort or page a “most recent conversations” view. Each returned thread is the conversation summarized, ready to render as a row.
Browse conversations from the CLI
The terminal command nylas email threads list shows recent conversations, and it’s the quick way to see what’s active without building anything. It defaults to 10 threads and takes --unread, --starred, and --subject to narrow the view, plus --limit to fetch more, mapping directly onto the API’s filters.
# Recent unread conversations
nylas email threads list --unread
# A specific conversation's full detail
nylas email threads show <thread-id>
Where list gives you the conversation summaries, nylas email threads show <thread-id> prints one thread’s detail — its subject, participants, message count, the earliest and latest dates, a snippet, and the list of message_ids (plus a draft count and status when present) — so you can see what the conversation holds at a glance in the terminal. This is useful for triage: scanning unread conversations and opening the one that matters, and checking what a thread actually contains before wiring thread handling into an app. The CLI mirrors the API’s read-and-organize model: it lists, shows, marks, and deletes threads, but it doesn’t send into them. To reply into a conversation you use nylas email send --reply-to <message-id>, which threads onto the message you’re answering.
Organize a whole conversation in one call
Here’s where threads earn their place: you can act on an entire conversation at once. A PUT /v3/grants/{grant_id}/threads/{thread_id} request updates the whole thread, so setting unread to false marks every message in the conversation read, starred stars the conversation, and a folders array moves all of its messages to a folder in a single operation.
curl --request PUT \
--url "https://api.us.nylas.com/v3/grants/<GRANT_ID>/threads/<THREAD_ID>" \
--header "Authorization: Bearer <NYLAS_API_KEY>" \
--header "Content-Type: application/json" \
--data '{ "unread": false, "starred": true }'
This is the operation that would be tedious at the message level. Marking a ten-message conversation read by hand means ten message updates; at the thread level it’s one PUT. The same goes for moving a conversation to an archive folder or starring it for follow-up — you act on the thread and every message inside it comes along. From the CLI, nylas email threads mark <thread-id> --read and --star do the same whole-conversation update, which is the natural way to clear or flag an entire exchange.
Deletion works the same way, on the whole conversation. A DELETE on the thread moves the entire exchange — every message included — to the Trash rather than erasing it outright, and nylas email threads delete <thread-id> does it from the terminal. A “delete conversation” action is one call rather than a loop over every message. As with everything at the thread level, the operation fans out to all the messages the thread holds: the conversation is the unit you act on.
Filter to the conversations that matter
A conversation view is only as good as its filtering, and threads support the filters that map onto real inbox actions. Beyond unread and starred for the obvious views, you filter by participant with from or any_email to find every conversation involving a particular person, by in to scope to a folder, and by has_attachment to surface conversations carrying files. Each narrows the thread list to a meaningful slice.
For ordering and paging a conversation view, the date filters are the ones you reach for. latest_message_after and latest_message_before bound the list by the conversation’s most recent activity, which is how a “recent conversations” screen stays current, and they pair with limit and cursor paging to walk a long inbox. Because the thread already carries latest_message_received_date, you have the value to sort on without opening any message. One provider caveat worth noting: Microsoft, IMAP, iCloud, Yahoo, and EWS return threads ordered reverse-chronologically by latest message received, but Google doesn’t guarantee that ordering due to a Gmail API limitation, so applications targeting multiple providers should sort on latest_message_received_date client-side to ensure consistent results.