Threads
Every operation in this group: what it accepts, what it returns and the errors it can answer with.
Operations
/threads/threads/{id}/threads/{id}/threads/{id}/threads/{id}/trash/threads/{id}/snooze/threads/{id}/unsnooze/threads/{id}/messages/{messageId}/attachments/threads/counts/threads/{id}/summary/threads/{id}/restore/threads/{id}/reply-suggestions/threads/{id}/mute/threads/{id}/unmute/follow-ups/follow-ups/follow-ups/{id}/sender-categories/sender-categories/{email}/sender-categories/{email}/threads/{id}/messages/threads/{id}/messages/{messageId}
GET/threads
List threads in a folder
Reads the local index. Passing query searches that same index.
Plain words must all appear, and each one matches loosely: case, accents and separators are ignored and part of a longer word counts, so min and ben jamin both find "Benjamin". A quoted phrase is matched as written apart from case and accents, so its separators have to line up: "ben jamin" does not find "Ben-Jamin", while "quarterly invoice" finds "Quarterly invoice". When nothing matches exactly, close spellings are returned instead, so benjimin finds "Benjamin": a plain word, or the value of from:, to:, cc:, subject:, body:, filename: or label:, may differ from the start of a word by one typo (a changed, missing, extra or swapped letter) when it has four to seven letters and by two when it has eight or more, while a quoted phrase, a word containing a digit, a shorter word and an excluded word still match exactly, and the pages that follow keep matching the same way. Filler words are dropped from a list of plain words when something else is left to search for, so mail server searches for server alone and emails from john searches for john. The filler is a, an, the, and, or, of, to, from, for, about, with, in, on, at, by, my, me, all, any, some, show, find, get, email, emails, mail, mails, message, messages, thread and threads, and it is kept when dropping it would leave nothing but a single letter.
Operators narrow the search: from:, to:, cc:, subject:, body:, label:, filename:, in: (inbox, sent, drafts, spam, trash, archive, snoozed, anywhere or a label name), is: (unread, read, starred, important, snoozed, muted, draft, sent, bounced for a delivery failure notice, unlabelled for a conversation that carries none of your labels), has: (attachment, pdf, image, video, audio, document, spreadsheet, presentation, userlabels, nouserlabels), after:YYYY/MM/DD, before:YYYY/MM/DD, newer_than:7d and older_than:1y (units h, d, w, m, y). Combine them with OR, AND, NOT, parentheses, braces for a set of alternatives ({stripe paddle}) and a leading - to exclude. Recipients are stored as one list without roles and never hold a Bcc, so cc: reads the same field as to: and bcc: matches nothing of its own. from:me is mail you sent, and to:me is mail carrying one of your own addresses, aliases included, among its recipients or as the address it was delivered to.
A value the search cannot use is ignored rather than narrowing, so a typo in a value widens the result instead of emptying it. Ignored: category:, larger:, smaller:, size:, messagesize:, list:, rfc822msgid:, received:, sent:, the category words (is:primary, is:personal, is:social, is:promotions, is:updates, is:forums, is:reservations, is:purchases), a has: word that names none of the kinds above, an importance: other than high or low, a date that cannot be read, and a duration whose unit is not h, d, w, m or y. An operator name it does not know, project: for instance, is searched as plain text instead.
Words and the from:, to:, cc:, subject: and body: operators read only the newest message on each thread: its sender, its recipients, its subject and the first 4,000 characters of its body. filename: and has: read every attachment on the whole conversation, and label:, in: and is: read the whole conversation. A plain word also matches the name of any attachment on the conversation, whichever message carried it. A message that arrived encrypted has no body text to match. folder still applies unless the query names one with in:, or with an is: that is a folder such as is:sent, and in:anywhere searches every folder, on its own as well as beside other terms. A drafts listing is the exception: GET /drafts and GET /threads?folder=draft stay in drafts whatever the query names.
Dates read the newest activity on the thread, in UTC. after: includes the day it names and before: excludes it. A date can be written YYYY/MM/DD, YYYY-MM-DD, YYYYMMDD, as a bare year, or as epoch seconds or milliseconds; A short date reads day first (16/09/2026), unless the second number cannot be a month (09/16/2026), and a number above 12 settles it either way. A day that does not exist is rejected and ignored.
Send semantic=true to match the plain words of query by meaning rather than by spelling. Describe the mail in a few words, such as flight to Berlin or invoices I still owe, in any language, and the threads closest in meaning come back best match first, together with every thread the words match as text, which ranks above a thread that only means the same. A thread is kept only when it is clearly closer to the words than the rest of the mailbox, so a description nothing fits returns only the text matches. Operators narrow the search exactly as above and folder and the other filters still apply, while sort does not. Meaning is read from the whole conversation, newest messages first and up to 12,000 characters, so an earlier message counts too. A conversation with an encrypted message is never read for meaning and is found by its text alone, new mail can be found by meaning about a minute after it arrives, and a workspace that turned search by meaning off in its settings (semanticSearch: false) gets a text search whatever semantic says. A query with no plain words, from:ada on its own for instance, is a text search whatever semantic says.
nextPageToken is opaque. Pass back what you were given, never construct one.
Requires the threads:read scope.
Query parameters
folderstringLabel the threads must carry, case insensitive. Defaults to
inbox, andbinis read astrash.Default"inbox"querystringMailbox search. Plain words must all appear and match loosely, ignoring case, accents and separators, against the newest message on each thread and the name of any attachment on it, while a quoted phrase has to appear as written and filler words such as
theoremailsare dropped when something else is left to search for. Operators such asfrom:ada,label:Invoices,is:unread,has:pdfandnewer_than:7dnarrow it, and naming a folder within:replacesfolder, soin:anywheresearches them all. When nothing matches exactly, close spellings are returned instead, sobenjiminfinds "Benjamin". A value the search cannot use is ignored rather than narrowing.labelIdsstringComma-separated label ids. A thread must carry every one of them and sit in
folder, which is how the app filters a folder by a label. To list a label's threads whichever folder they are in, pass its id asfolderinstead:folder=USER_BIG_CLIENTS.sortstringThe four orders of the thread list:
newest,oldest,senderandsubject.senderandsubjectare alphabetical, newest first within one sender or subject. Threads that arrived in the same instant are ordered by id, so every order pages to the end without skipping or repeating one.One of"newest""oldest""sender""subject"Default"newest"dateFromstringKeeps threads whose newest message arrived at or after this instant. ISO 8601 with a time and an offset, such as
2026-09-01T00:00:00Z.Formatdate-timedateTostringKeeps threads whose newest message arrived at or before this instant. Both ends are included, and
dateFromafterdateTois a 422.Formatdate-timefromContactsstringtruekeeps only threads whose newest message came from a saved contact, the From contacts filter of the thread list. A key limited to particular addresses reads the contacts its owner saved, and an app acting for a member reads the contacts that member saved.One of"true""false"semanticstringtruematches the plain words ofqueryby meaning instead of spelling, in any language, and returns the closest threads best match first along with every thread the words match as text. Operators and the other filters still apply andsortdoes not. It changes nothing whenqueryhas no plain words or when the workspace has turned search by meaning off.One of"true""false"categorystringKeep only the threads sorted into this inbox tab:
primary,promotions,updates,socialorforums.primaryis every thread in no other tab. Inquery,category:promotionsfilters the same way in any folder.One of"primary""promotions""updates""social""forums"addressstringKeep only the threads delivered to this address, the address filter of the thread list. One the key does not reach lists nothing rather than failing.
3 to 320 characterslimitintegerThreads per page, a whole number from 1 to 100. Defaults to 25.
At least 1At most 100pageTokenstringThe previous page's
nextPageToken, passed back as it came. Send the samesort,semantic, dates and filters with it.
Returns
A page of threads.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
threads.list()threads.listAll()threads.iterate()- Python
threads.list()threads.list_all()threads.iterate()- Ruby
threads.listthreads.list_allthreads.iterate- PHP
threads->listthreads->listAllthreads->iterate- Go
Threads.ListThreads.ListAllThreads.Iterate- Java
threads().listthreads().listAllthreads().iterate- C#
Threads.ListAsyncThreads.ListAllAsyncThreads.IterateAsync- CLI
openemail threads list- MCP
listThreads
GET/threads/{id}
Retrieve a thread
Every message in it, not only the most recent.
The messages are passed through as the mailbox stored them rather than projected onto a field list, and this document describes only encryption, which is the one field whose absence a caller cannot safely guess at. A message that arrived encrypted carries it and has an EMPTY BODY; read encryption.format before concluding a message was empty, and note that the two *-signed formats are ordinary readable mail.
Requires the threads:read scope.
Path parameters
idstringRequiredThread id, as returned by
listor carried on a message.
Returns
The thread, with every message on it.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
threads.get()- Python
threads.get()- Ruby
threads.get- PHP
threads->get- Go
Threads.Get- Java
threads().get- C#
Threads.GetAsync- CLI
openemail threads get- MCP
getThread
PATCH/threads/{id}
Mark read/unread and change labels
Applies and removes labels, and sets read state, on one conversation. Label ids come from GET /labels; the system ids INBOX, ARCHIVE, STARRED, IMPORTANT, SPAM and UNREAD are taken in any case, so archiving is addLabelIds: ["ARCHIVE"] with removeLabelIds: ["INBOX"]. MUTE in addLabelIds mutes the thread as POST /threads/{id}/mute does, and the tab labels CATEGORY_PROMOTIONS, CATEGORY_UPDATES, CATEGORY_SOCIAL and CATEGORY_FORUMS move a thread between inbox tabs. An id in addLabelIds that names no label is a 422 label_not_found, and nothing on the thread changes: create the label with POST /labels first. An unknown id in removeLabelIds is not an error, since the thread does not carry it. TRASH, SNOOZED and DRAFT are refused with label_not_directly_settable; use the trash and snooze endpoints.
Requires the threads:write scope.
Path parameters
idstringRequiredThread id.
Request body
readbooleantrueremovesUNREADandfalseadds it.addLabelIdsstring[]Label ids to put on the thread. Each must name a label.
Up to 50 itemsremoveLabelIdsstring[]Label ids to take off the thread.
Up to 50 items
Returns
Applied.
Errors
- 422
label_not_foundfor an id inaddLabelIdsthat names no label, orlabel_not_directly_settableforTRASH,SNOOZEDorDRAFT.
The errors every operation can return400401403404429500Error catalog
Also available in
DELETE/threads/{id}
Delete a thread for good
Deletes every message in the thread, with its attachments, and cannot be undone, which is what Delete from Bin does in the app. It works on a thread in any folder, so move it to the Bin with POST /threads/{id}/trash when you only mean to throw it away.
Requires the threads:write scope.
Path parameters
idstringRequiredThe thread, as
GET /threadsreturns it.
Returns
Gone for good.
Errors
- 500
thread_delete_failed: the thread could not be deleted, and nothing was removed.
The errors every operation can return400401403404422429Error catalog
Also available in
POST/threads/{id}/trash
Move a thread to the Bin
Clears INBOX, SPAM, SNOOZED and ARCHIVE together, which is what the app does. Adding the TRASH label by hand through PATCH is refused, because doing only half of it leaves the thread listed in the Bin AND its old folder.
Requires the threads:write scope.
Path parameters
idstringRequiredThread id.
Returns
Trashed.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
threads.trash()- Python
threads.trash()- Ruby
threads.trash- PHP
threads->trash- Go
Threads.Trash- Java
threads().trash- C#
Threads.TrashAsync- CLI
openemail threads trash- MCP
trashThreads
POST/threads/{id}/snooze
Snooze a thread
Hides it and schedules its return. Both halves happen together: the label hides it, a stored wake time brings it back, and a thread snoozed by label alone never returns.
Requires the threads:write scope.
Path parameters
idstringRequiredThread id.
Request body
wakeAtstringRequired- Format
date-time
Returns
Snoozed.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
threads.snooze()- Python
threads.snooze()- Ruby
threads.snooze- PHP
threads->snooze- Go
Threads.Snooze- Java
threads().snooze- C#
Threads.SnoozeAsync- CLI
openemail threads snooze- MCP
snoozeThreads
POST/threads/{id}/unsnooze
Unsnooze a thread
Brings it back now and cancels the scheduled return.
Requires the threads:write scope.
Path parameters
idstringRequiredThread id.
Returns
Back in the inbox, with snoozedUntil null.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
GET/threads/{id}/messages/{messageId}/attachments
A message's attachments
content is base64, and an empty string where the stored bytes could not be found.
This is the DISPLAY list. Of the parts named by a message's encryption.parts, the ciphertext part IS listed and downloads like any other file (it is the message, and downloading it is the only way an API client reads this mail). The PGP/MIME version part and any detached signature are held out deliberately, because they rendered as junk attachments and there is nothing a caller can do with them. Both keep their ids in encryption.parts, which correlates the two views, and this endpoint does not return them.
Requires the threads:read scope.
Path parameters
idstringRequiredThread id the message belongs to.
messageIdstringRequiredMessage id from that thread's
messages.
Returns
Attachments.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
GET/threads/counts
Count the mail in each folder
What the sidebar of the app shows: how many conversations each folder holds and how many of them are unread, how many drafts are waiting, how many conversations each of your labels has in each folder, how many inbox conversations sit in each tab, and how many inbox conversations arrived at each address. unread is a pseudo-folder whose count is the unread conversations in the inbox, and draft counts drafts, which are never unread. A key or an app limited to particular addresses counts only the mail that arrived at them and the drafts written from them, and address narrows the counts to one address, never wider than that.
Requires the threads:read scope.
Query parameters
addressstringCount only the mail delivered to this address. One the key does not reach counts nothing rather than failing.
3 to 320 characters
Returns
The counts.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
GET/threads/{id}/summary
Read the summary of a thread
The short AI summary the reading pane shows above a thread. It is written the first time it is asked for and then kept, and the first request after a newer message arrives writes it again, so reading it is cheap. state is ready with the summary, pending while the first one is being written (ask again in a few seconds), or none when there is nothing to summarise, such as a thread holding a message that arrived encrypted. While a newer summary is being written, the one before it comes back as ready. Summaries spend no AI actions.
Requires the threads:read scope.
Path parameters
idstringRequiredThe thread, as
GET /threadsreturns it.
Returns
The summary, or the state it is in.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
POST/threads/{id}/restore
Take a thread out of the Bin
Puts a thread back in the inbox, out of the Bin and out of Spam, which is what Restore from Bin and Move to inbox do in the app. It is the undo of POST /threads/{id}/trash, and calling it again changes nothing.
Requires the threads:write scope.
Path parameters
idstringRequiredThe thread, as
GET /threadsreturns it.
Returns
Back in the inbox.
Errors
- 500
thread_update_failed: the labels could not be written, and nothing on the thread changed.
The errors every operation can return400401403404422429Error catalog
Also available in
GET/threads/{id}/reply-suggestions
Suggest replies to a thread
Up to 3 short replies to the latest message of the thread, the ones the reading pane offers under it. Each has a label, a few words that say what it answers, and a body, the reply itself, ready to send with POST /emails or to keep with POST /drafts. They are written in the language of that message and the voice of the mail this workspace sends, from the conversation, what was written to this correspondent before, what is known about their organisation and, when the message asks for a time, the free and busy times of the calendar.
Suggestions are written once for each new message and kept, so asking again is free until another message arrives. state is ready with the suggestions, pending while they are being written (ask again in a few seconds), or none when the message needs no reply: your own reply came last, or it is a newsletter, an automated notice, mail from a no-reply address, spam or an encrypted message, and every thread is none in a workspace that turned replySuggestions off in its settings. messageId names the message they answer. Writing suggestions spends no AI action, and a workspace has suggestions written for at most 500 new messages a day.
Requires the threads:read scope.
Path parameters
idstringRequiredThe thread, as
GET /threadsreturns it.
Returns
The suggested replies, or the state they are in.
Errors
- 409
ai_not_configured: suggesting replies is not available on this server.
The errors every operation can return400401403404422429500Error catalog
Also available in
POST/threads/{id}/mute
Mute a thread
Mutes a conversation, as Mute does in the app. A thread in the inbox moves to the archive, and from then on new mail in it goes straight to the archive, unread, with no push, sound or desktop notice. A webhook for that mail carries muted: true. Thread state belongs to the whole workspace, so the thread is muted for everyone on it, and mail addressed only to you does not bring it back. Calling it again changes nothing. PATCH /threads/{id} with MUTE in addLabelIds does the same.
Requires the threads:write scope.
Path parameters
idstringRequiredThe thread, as
GET /threadsreturns it.
Returns
Muted, with muted true.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
threads.mute()- Python
threads.mute()- Ruby
threads.mute- PHP
threads->mute- Go
Threads.Mute- Java
threads().mute- C#
Threads.MuteAsync- CLI
openemail threads mute- MCP
muteThreads
POST/threads/{id}/unmute
Unmute a thread
Takes the mute off, so the next message in the thread arrives in the inbox and alerts people again. The thread stays where it is: move it back with PATCH /threads/{id} and INBOX if you want it in the inbox now. A thread that is not muted is left as it is.
Requires the threads:write scope.
Path parameters
idstringRequiredThe thread, as
GET /threadsreturns it.
Returns
Not muted any more, with muted false.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
threads.unmute()- Python
threads.unmute()- Ruby
threads.unmute- PHP
threads->unmute- Go
Threads.Unmute- Java
threads().unmute- C#
Threads.UnmuteAsync- CLI
openemail threads unmute- MCP
unmuteThreads
GET/follow-ups
List reminders
The reminders you set on mail you sent, newest first: the ones still waiting for a reply and the ones that ended. A reminder is waiting until somebody replies (answered), its time comes with no reply (due, and the thread is back at the top of the inbox, unread) or it is cancelled. An API key lists the reminders of the workspace owner, and an app those of the person who connected it. A key or an app limited to particular addresses lists only reminders on threads delivered to them.
Requires the threads:read scope.
Query parameters
statusstringOnly reminders in this state.
One of"waiting""answered""due""cancelled"threadIdstringOnly the reminders on this thread.
1 to 256 characterslimitintegerHow many to return, newest first. 100 when left out.
At least 1At most 200
Returns
The reminders.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
POST/follow-ups
Set a reminder on a thread
Asks to be reminded about a thread if nobody replies by remindAt. If a real reply arrives first, the reminder closes by itself. Otherwise, when the time comes, the thread returns to the top of the inbox, unread, with the built-in FOLLOW_UP label and a push to your phones. Automatic replies, delivery reports and your own sending address do not count as a reply. A thread holds one waiting reminder per person, so setting another moves the time. To set one as you send, pass followUpAt to POST /emails instead.
Requires the threads:write scope.
Request body
threadIdstringRequiredThe thread to be reminded about, as
threads.listreturns it.1 to 256 charactersremindAtstringRequiredWhen the thread comes back if nobody replied: a future instant, at most 365 days out.
Formatdate-time
Returns
The reminder, waiting.
Errors
- 422
invalid_follow_up:remindAtis not in the future or is more than 365 days out, or somebody has already replied in the thread since your last message.
The errors every operation can return400401403404429500Error catalog
Also available in
- TypeScript
followUps.set()- Python
follow_ups.set()- Ruby
follow_ups.set- PHP
followUps->set- Go
FollowUps.Set- Java
followUps().set- C#
FollowUps.SetAsync- CLI
openemail follow-ups set- MCP
setFollowUp
DELETE/follow-ups/{id}
Cancel a reminder
Cancels a waiting reminder, so the thread stays where it is when the time comes. A reminder that already ended is returned as it is. Only your own reminders can be cancelled.
Requires the threads:write scope.
Path parameters
idstringRequiredThe reminder, as the list returns it.
Returns
The reminder as it is now.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
GET/sender-categories
List sender choices
The senders whose mail always goes to one inbox tab, by address. New mail is sorted into Primary, Promotions, Updates, Social or Forums by plain rules, and a choice saved here wins over them. GET /threads takes category to read one tab.
Requires the threads:read scope.
Returns
The choices, by sender.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
threads.listSenderCategories()- Python
threads.list_sender_categories()- Ruby
threads.list_sender_categories- PHP
threads->listSenderCategories- Go
Threads.ListSenderCategories- Java
threads().listSenderCategories- C#
Threads.ListSenderCategoriesAsync- CLI
openemail threads list-sender-categories- MCP
listSenderCategories
PUT/sender-categories/{email}
Always sort a sender into one tab
Saves the tab for one sender and moves the inbox threads whose newest message is from them, up to the newest 1,000, into it. Mail from them is sorted there from now on, whatever the rules would say. Saving again replaces the choice. A workspace holds at most 2,000 choices, and they apply to every address in it, so a key or an app limited to particular addresses or domains can read them but not change them.
Requires the threads:write scope.
Path parameters
emailstringRequiredThe address of the sender, compared without case.
Up to 320 charactersFormatemail
Request body
categorystringRequired- One of
"primary""promotions""updates""social""forums"
Returns
Saved.
Errors
- 422
invalid_parameter: the address or the category is not valid.sender_category_limit_reached: the workspace holds as many choices as it may.capability_unsupported: the key or app is limited to particular addresses or domains.
The errors every operation can return400401403404429500Error catalog
Also available in
DELETE/sender-categories/{email}
Remove a sender choice
Removes the choice, so new mail from the sender is sorted by the rules again. Threads already sorted stay where they are.
Requires the threads:write scope.
Path parameters
emailstringRequiredThe address of the sender, compared without case.
Up to 320 charactersFormatemail
Returns
Removed.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
- TypeScript
threads.clearSenderCategory()- Python
threads.clear_sender_category()- Ruby
threads.clear_sender_category- PHP
threads->clearSenderCategory- Go
Threads.ClearSenderCategory- Java
threads().clearSenderCategory- C#
Threads.ClearSenderCategoryAsync- CLI
openemail threads clear-sender-category- MCP
clearSenderCategory
GET/threads/{id}/messages
List the messages of a thread
Every message on the thread, oldest first, each as a MailboxMessage with its sender, recipients, date, Message-ID, text and HTML body, attachments and headers. Unsent draft replies are in the list with draft: true. The fields here are a contract: they are added to, never renamed or taken away, whatever the mailbox stores underneath. GET /threads/{id} still returns each message the way the mailbox stored it.
Requires the threads:read scope.
Path parameters
idstringRequiredThe thread, as
GET /threadsreturns it.
Returns
The messages.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
GET/threads/{id}/messages/{messageId}
Read one message of a thread
One message, in the shape GET /threads/{id}/messages lists. The thread is checked first and then the message, so a message that is not on that thread is a 404 even when it exists elsewhere in the mailbox. The fields here are a contract: they are added to, never renamed or taken away, whatever the mailbox stores underneath. GET /threads/{id} still returns each message the way the mailbox stored it.
Requires the threads:read scope.
Path parameters
idstringRequiredThe thread, as
GET /threadsreturns it.messageIdstringRequiredThe message, as
idon one of the messagesGET /threads/{id}/messagesreturns.
Returns
The message.
Errors
The errors every operation can return400401403404422429500Error catalog
Also available in
Objects
DeletedSenderCategoryobject
objectstringRequired- One of
"sender_category" senderstringRequired- Format
email deletedbooleanRequired- One of
true
DeletedThreadobject
objectstring- One of
"thread" idstringdeletedboolean- One of
true
FollowUpobject
objectstringRequired- One of
"follow_up" idstringRequiredthreadIdstringRequiredThe thread. Null until a scheduled message with a reminder goes out.
Can be nullmessageIdstringRequiredThe Message-ID of the mail the reminder is about, once it went out.
Can be nullemailIdstringRequiredThe email, as
GET /emailslists it, when the reminder was set withfollowUpAt.Can be nullsubjectstringRequiredThe subject of the thread.
Can be nullremindAtstringRequiredWhen the thread comes back if nobody replied.
Formatdate-timestatusstringRequiredwaitingfor a reply,answeredby one,duewhen the time came with none, orcancelled.One of"waiting""answered""due""cancelled"answeredAtstringRequiredWhen the reply arrived. Null unless
answered.Can be nullFormatdate-timefiredAtstringRequiredWhen the thread was brought back. Null unless
due.Can be nullFormatdate-timecreatedAtstringRequiredWhen the reminder was set.
Formatdate-time
FollowUpListobject
objectstring- One of
"list" dataFollowUp[]
KnowledgeSourceRefobject
idstringRequiredThe knowledge item, as
GET /knowledge/{id}reads it.titlestringRequiredkindstringRequired- One of
"note""file""link" scopestringRequiredA
scopeis empty for the whole workspace,@and a domain for everything on that domain (@acme.com), or one address ([email protected]). The AI working for an address reads that address, then its domain, then the whole workspace, the most specific first.urlstringRequiredThe page a link item reads, or null.
Can be null
MailboxCountsobject
objectstringRequired- One of
"mailbox_counts" foldersobject[]RequiredlabelstringRequiredThe folder, as its label id in lower case.
One of"inbox""sent""spam""archive""trash""snoozed""draft""unread"countintegerRequiredConversations in the folder. For
draft, the drafts waiting to be sent.unreadintegerRequiredHow many of them are unread. Always 0 for
draft.
labelsobject[]RequiredOne row for each of your labels and each folder it has conversations in. A label with no conversations in a folder has no row for it.
idstringRequiredThe label, as
GET /labelslists it.folderstringRequiredThe folder, as its label id in lower case.
One of"inbox""sent""spam""archive""trash""snoozed"countintegerRequiredConversations in the folder that carry the label.
unreadintegerRequiredHow many of them are unread.
categoriesobject[]RequiredOne row for each inbox tab, counting inbox conversations only.
primaryis everything not sorted into another tab.categorystringRequiredThe tab.
One of"primary""promotions""updates""social""forums"countintegerRequiredInbox conversations in the tab.
unreadintegerRequiredHow many of them are unread.
unlabelledobjectRequiredThe inbox conversations that carry none of your labels, which is what the inbox of the app lists unless Labelled mail is ticked in its Filters menu. A conversation the default rule labelled
BOUNCEDcounts as labelled.countintegerRequiredInbox conversations with no label.
unreadintegerRequiredHow many of them are unread.
categoriesobject[]RequiredThe same count for each inbox tab.
categorystringRequiredThe tab.
One of"primary""promotions""updates""social""forums"countintegerRequiredInbox conversations in the tab with no label.
unreadintegerRequiredHow many of them are unread.
addressesobject[]RequiredaddressstringRequiredThe address the mail arrived at, or null for mail that recorded none.
Can be nullcountintegerRequiredInbox conversations delivered to it.
teamobjectRequiredThe team views of a shared inbox, counted for the person the key or app acts for. A conversation is open while it is in the inbox, and marking it done archives it.
assignedobjectRequiredOpen conversations assigned to you.
countintegerRequiredOpen conversations assigned to you.
unreadintegerRequiredHow many of them are unread.
unassignedobjectRequiredOpen conversations nobody is assigned to.
countintegerRequiredOpen conversations nobody has.
unreadintegerRequiredHow many of them are unread.
mentionsobjectRequiredConversations outside Spam and the bin where a team comment mentions you.
countintegerRequiredConversations that mention you.
unreadintegerRequiredHow many of them are unread.
unseenintegerRequiredHow many of them have a mention newer than the last time you opened their comments in the app.
MailboxMessageobject
A message in the mailbox, in a shape that does not change with how it is stored. The fields here are a contract: they are added to, never renamed or taken away, whatever the mailbox stores underneath. GET /threads/{id} still returns each message the way the mailbox stored it.
objectstringRequired- One of
"message" idstringRequiredThe message,
msg_and 24 hex characters, for the message and attachment routes.threadIdstringRequiredThe conversation it belongs to.
messageIdstringRequiredIts Message-ID header, angle brackets included. A message that arrived without one has one Nextmail made up.
Can be nullfromobjectRequiredWho it is from.
namestringRequiredThe display name, or null when there was none.
Can be nullemailstringRequiredThe address.
toobject[]RequiredIts To recipients.
namestringRequiredThe display name, or null when there was none.
Can be nullemailstringRequiredThe address.
ccobject[]RequiredIts Cc recipients.
namestringRequiredThe display name, or null when there was none.
Can be nullemailstringRequiredThe address.
bccobject[]RequiredIts Bcc recipients. Only mail the workspace sent knows them, so mail that arrived has none.
namestringRequiredThe display name, or null when there was none.
Can be nullemailstringRequiredThe address.
replyToobject[]RequiredWhere a reply goes, when that is not the sender.
namestringRequiredThe display name, or null when there was none.
Can be nullemailstringRequiredThe address.
subjectstringRequiredThe subject line,
(no subject)when it had none.datestringRequiredWhen it was written, from its Date header. Null when that header could not be read.
Can be nullFormatdate-timedraftbooleanRequiredTrue for a reply written in the app and not sent yet.
inReplyTostringRequiredThe Message-ID of the message it answers, from its In-Reply-To header.
Can be nullreferencesstring[]RequiredThe Message-IDs of its References header, oldest first.
textstringRequiredThe body as plain text. For a message that came as HTML only, this is the text drawn from that HTML, with links written out after their words. Null when it has no body.
Can be nullhtmlstringRequiredThe HTML body as it arrived or was sent, unprocessed. Null for a plain text message.
Can be nullattachmentsobject[]RequiredIts attachments, without their bytes. An image shown inside the body is not listed. Read the bytes with
GET /threads/{id}/messages/{messageId}/attachments.idstringRequiredThe attachment, as
attachmentIdin the attachment list.filenamestringRequiredmimeTypestringRequiredsizeintegerRequiredIts size in bytes.
headersobject[]RequiredThe headers Nextmail keeps of the message, written out as it would send them: From, To, Cc, Reply-To, Subject, Date, Message-ID, In-Reply-To, References, List-Unsubscribe, List-Unsubscribe-Post. A header the message did not have is left out. The rest of what it arrived with is not kept.
namestringRequiredvaluestringRequired
encryptionobjectRequiredSet when the message is signed or encrypted.
sealedis true when its body cannot be read without the key, sotextholds armour or nothing. Null for ordinary mail.Can be nullformatstringRequired- One of
"pgp-mime""pgp-signed""pgp-inline""smime-encrypted""smime-signed" sealedbooleanRequired
MailboxMessageListobject
objectstring- One of
"list" dataMailboxMessage[]
MessageAttachmentListobject
objectstringRequired- One of
"list" dataobject[]RequiredobjectstringRequired- One of
"attachment" attachmentIdstringRequiredfilenamestringRequiredcontentTypestringRequiredsizeintegerRequiredBytes.
At least 0contentstringRequiredThe bytes, base64. An empty string when the stored bytes could not be found.
Formatbyte
MessageEncryptionobject
What kind of encryption this message arrived carrying, read off its top-level Content-Type when it was ingested.
ABSENCE MEANS NOBODY LOOKED. The field is omitted on every message stored before detection existed. It never means "checked, and found none". A client reading a missing encryption as "this was plaintext" is asserting something no part of this system measured.
It is a statement about the ENVELOPE and not a verification. A message can be seen to be sealed without being opened, and those are different claims: nothing here says a signature checked out, says who holds a key, or licenses a padlock in a user interface.
SIGNED IS NOT SEALED, and that distinction is the whole reason format is an enum rather than a flag. For the three sealed formats the message's body fields are empty or hold PGP armor, and an empty body on such a message means "we cannot read this" rather than "there was nothing here". For the two signed formats the body is ordinary text and every consumer keeps working on it.
formatstringRequiredWhich of the five envelope shapes was recognised. THREE of them mean the body is unreadable (
pgp-mime,pgp-inlineandsmime-encrypted), and the other two,pgp-signedandsmime-signed, mean the body arrived in the clear beside a detached signature and is read exactly like any other message. Branch on this value; branching on the mere presence of the object treats readable mail as unreadable.One of"pgp-mime""pgp-signed""pgp-inline""smime-encrypted""smime-signed"detectedAtstringRequiredWhen the classification was made, on our clock at ingest. It dates OUR READING of the envelope and says nothing about when the message was encrypted or signed, or by whom.
rawRetainedbooleanWhether the original RFC822 bytes were kept, so that something holding the key could open the message later. Today it is always false. Nothing retains the raw message yet. It is published now so a client can start reading it rather than needing a second pass over every stored message the day that changes.
Defaultfalsepartsobject[]The envelope parts, each named by the id its bytes were written under. They are deliberately NOT in the message's attachment list (a PGP/MIME message would otherwise render two junk chips), so a sealed message commonly reports no attachments at all, and that emptiness is not evidence that nothing was attached. These ids are a CORRELATION KEY and not a handle:
GET /threads/{id}/messages/{messageId}/attachmentsserves the display list, so it does not return them, and no other path in this API returns them either.indexis the index into the original MIME parts.indexintegerRequiredattachmentIdstringRequiredrolestringRequired- One of
"version""ciphertext""signature"
MutedThreadobject
objectstringRequired- One of
"thread" idstringRequiredmutedbooleanRequiredWhether the thread is muted now.
ReplySuggestionobject
labelstringRequiredWhat the reply says, in a few words, as a button would show it.
Up to 60 charactersbodystringRequiredThe reply in plain text, with no subject and no signature.
Up to 1200 characters
ReplySuggestionsobject
objectstringRequired- One of
"reply_suggestions" threadIdstringRequiredmessageIdstringRequiredThe message the suggestions answer, the latest one in the thread.
Can be nullstatestringRequired- One of
"ready""pending""none" suggestionsReplySuggestion[]RequiredEmpty unless
stateisready.Up to 3 itemssourcesKnowledgeSourceRef[]RequiredThe knowledge base items the suggestions drew on: pinned notes at the levels of the receiving address and the passages that matched the message. Empty when none were used.
RestoredThreadobject
objectstring- One of
"thread" idstringrestoredboolean- One of
true
SenderCategoryobject
objectstringRequired- One of
"sender_category" senderstringRequiredThe sender, in lower case.
FormatemailcategorystringRequiredThe inbox tab their mail goes to.
One of"primary""promotions""updates""social""forums"updatedAtstringRequiredThe last save.
Formatdate-time
SenderCategoryListobject
objectstring- One of
"list" dataSenderCategory[]
Threadobject
objectstring- One of
"thread" idstringThe id that was asked for. Echoed back rather than read off the result. The mailbox answers a thread as messages, labels and counts, with no id of its own.
messagesThreadMessage[]Every message on the thread, oldest first, not only the most recent.
labelsobject[]The folders and labels the thread sits in. Backend-shaped like the messages are, so only the two fields every backend agrees on are described.
idstringnamestring
messageCountintegerThe length of
messages, counted here.hasUnreadbooleantotalRepliesintegerdeliveredTostringThe workspace address the thread belongs to, lower-cased: the address its first message was delivered to, with any
+tagremoved, or the address it was sent from when the thread began with a send. Later messages joining the thread do not change it. Null when no address was recorded.Can be null
ThreadLabelChangeobject
objectstringRequired- One of
"thread" idstringRequiredaddedLabelIdsstring[]RequiredThe labels put on, with
UNREADwhenreadwas false.removedLabelIdsstring[]RequiredThe labels taken off, with
UNREADwhenreadwas true. Unknown ids are passed through.updatedThreadIdsstring[]RequiredThe conversations the change was written to.
failedThreadIdsstring[]RequiredThe conversations it could not be written to.
ThreadListobject
objectstringRequired- One of
"list" dataobject[]RequiredobjectstringRequired- One of
"thread" idstringRequired
nextPageTokenstringRequiredPass it back as
pageTokenfor the next page. Null on the last page.Can be null
ThreadMessageobject
A message, in whatever shape the mailbox stored it. The fields are deliberately not enumerated here. encryption, sentWith, forwardedFrom and verificationCode are the properties this API describes, because their absence cannot be guessed at safely.
encryptionMessageEncryptionsentWithstringWhat the message was sent with, read from its headers when it arrived: a mail provider such as
gmailoroutlook, a sending service such asresendorsendgrid,openemailfor mail sent through Nextmail, oramazon-seswhen only the network it came through is known. Absent when nothing in the message says.One of"openemail""gmail""outlook""icloud""yahoo""proton""zoho""fastmail""resend""sendgrid""mailgun""postmark""mailchimp""brevo""sparkpost""hubspot""amazon-ses"forwardedFromstringThe address the message was first sent to, when it reached you through forwarding rather than directly. A reply cannot come from that address unless it is one of yours. Null when the message came to you directly, and absent on messages that arrived before this was recorded.
Can be nullverificationCodestringThe sign-in or verification code the message carries, as you would type it: digits only, or capital letters and digits, without spaces or dashes. Set only when Nextmail is sure: the sender passed authentication, nothing about the message was flagged, it was not filed to Spam or Trash, and it holds exactly one code worded as one. Null when the message carries none or any of that is in doubt. Absent where a listing leaves out message bodies and the message arrived before codes were read.
Can be null
ThreadSnoozeobject
objectstringRequired- One of
"thread" idstringRequiredsnoozedUntilstringRequiredWhen the thread comes back to the inbox. Null once it is unsnoozed.
Can be nullFormatdate-time
ThreadSummaryobject
objectstringRequired- One of
"thread_summary" threadIdstringRequiredstatestringRequiredreadywhen the summary is written.pendingwhile it is being written, so ask again in a few seconds.failedwhen the last try did not work, and asking again tries once more.nonewhen there is nothing to summarise.One of"ready""pending""failed""none"summarystringRequiredOne or two sentences, present when
stateisready.Can be null
TrashedThreadobject
objectstringRequired- One of
"thread" idstringRequiredtrashedbooleanRequired- One of
true