Skip to the documentation
C#

client.Threads

Every method in this namespace: its signature, its parameters, what it returns and an example.

Methods

Read, search, label, trash, snooze and delete conversations in the mailbox, with their attachments, notes and summaries.

Threads.ListAsync

List one page of threads in a folder

Scopesthreads:readPages through results
Signature
Task<Page> ListAsync(    string? folder = null,    string? query = null,    IEnumerable<string>? labelIds = null,    string? sort = null,    DateTimeOffset? dateFrom = null,    DateTimeOffset? dateTo = null,    bool? fromContacts = null,    bool? semantic = null,    string? category = null,    string? address = null,    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns one page of threads from the mailbox index, ordered by each thread's latest message with the newest first unless sort: says otherwise. A row is only object and id, so call GetAsync for the messages, labels and unread state.

sort:, dateFrom:, dateTo: and fromContacts: are the thread list's own controls: the four orders, a date range read against the newest message on each thread, and a filter to mail from saved contacts. Every order pages to the end, and a cursor carries on in the order it was handed out in, so send the same filters with it.

folder: defaults to inbox and is matched against label ids after being upper cased, so sent, archive, spam, trash, draft, snoozed, starred and unread all work, bin is read as trash, and a user label id works as a folder too. A name that matches nothing returns an empty page rather than an error. labelIds: narrows the folder further: a thread must carry the folder label and every id you pass.

query: takes the mailbox search syntax. Plain words must all appear, and each matches loosely: case, accents and separators are ignored and part of a longer word counts, so min finds "Benjamin". A quoted phrase is matched as written apart from case and accents, so "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 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 such as the, about or emails are dropped from a list of plain words when something else is left to search for, so emails from john searches for john alone. Operators such as from:, to:, subject:, label:, is:unread, has:pdf, after:2026/01/31 and newer_than:7d narrow it, and OR, parentheses and a leading - combine them. 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.

With semantic: true the plain words of query: match by meaning rather than spelling. Describe the mail in a few words, 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 one 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 and filters narrow it as usual, sort: does not apply, and a query with no plain words is a text search whatever semantic: says. Meaning is read from the whole conversation, newest messages first, so an earlier message counts too, and a workspace that turned search by meaning off in its settings gets a text search 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 with markup stripped. 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 sealed message has no body text to match. The search stays inside folder: unless the query names a folder itself, with in: or a folder is: 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 and stays 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, and 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. The API pages with an opaque pageToken, which the SDK hands back as nextCursor and accepts as cursor:.

Parameters

folderstring?

Label the threads must carry, case insensitive. Defaults to inbox, and bin is read as trash.

querystring?

Mailbox search. Plain words must all appear and match loosely, a quoted phrase has to appear as written, filler words are dropped when something else is left to search for, and operators such as from:ada, has:pdf and in:anywhere narrow it.

labelIdsIEnumerable<string>?

Label ids a thread must all carry on top of folder:, matched exactly. A dictionary is sent comma separated.

sortstring?

newest (the default), oldest, sender or subject, the four orders of the thread list in the app, as in Nextmail\Constants\ThreadSorts. sender and subject are alphabetical, newest first within one sender or subject.

dateFromDateTimeOffset?

Keeps threads whose newest message arrived at or after this instant. A DateTimeOffset.

dateToDateTimeOffset?

Keeps threads whose newest message arrived at or before this instant. Both ends are included, and dateFrom: after dateTo: is a 422.

fromContactsbool?

When true, keeps only threads whose newest message came from a saved contact. A key limited to some addresses reads the contacts its owner saved.

semanticbool?

When true, the plain words of query: match by meaning instead of spelling, in any language, and the closest threads come first along with every thread the words match as text. Operators and the other filters still apply and sort: does not. Ignored when the workspace has search by meaning turned off.

categorystring?

Keeps only the threads sorted into this inbox tab: primary, promotions, updates, social or forums. primary is every thread in no other tab. In query, category:promotions filters the same way in any folder.

addressstring?

Keeps only the threads delivered to this address, the address filter of the thread list. One the key does not reach returns nothing rather than failing.

limitint?

Threads per page, a whole number from 1 to 100. Defaults to 25.

cursorstring?

The nextCursor of the previous page, passed back unchanged.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A Page with items, hasMore and nextCursor. Each item has object set to thread and id.

Example

using Nextmail.Constants; var page = await client.Threads.ListAsync(folder: "inbox", query: "from:ada has:pdf", limit: 50); foreach (var summary in page){    Console.WriteLine($"{summary["id"]}");} var lastWeek = await client.Threads.ListAsync(sort: ThreadSorts.Oldest, dateFrom: DateTimeOffset.UtcNow.AddDays(-7), fromContacts: true); Console.WriteLine($"{(lastWeek.HasMore ? "More than a page" : "One page")} from contacts this week"); var trip = await client.Threads.ListAsync(folder: "archive", query: "flight to Berlin", semantic: true); Console.WriteLine($"{trip.Items[0]?["id"]?.ToString() ?? "no match"}");

Notes

  • The server offers a cursor whenever a page comes back full, so hasMore can be true on what turns out to be the last page, and the next call then returns no items.

  • Threads that arrived in the same instant are ordered by id, so a page boundary between two of them never skips or repeats one.

  • A thread that receives mail while you page moves ahead of the cursor and is not returned by later pages.

  • A value query: cannot use is ignored rather than narrowing, so a typo in a value widens the result instead of emptying it. That covers category:, larger:, smaller:, size:, messagesize:, list:, rfc822msgid:, received: and sent:, the category words such as is:promotions, a has: word naming no kind of attachment, an importance: other than high or low, an unreadable date 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.

  • A narrowed key only sees threads delivered to the addresses it covers, every address on a whole domain it holds included. A thread still has to carry the folder and every one of labelIds:, the same as for any other key.

Also available in

API
GET /threads
TypeScript
nextmail.threads.list()
Python
threads.list()
Ruby
threads.list
PHP
threads->list
Go
Threads.List
Java
threads().list
CLI
nextmail threads list

Threads.ListAllAsync

Collect every thread in a folder into one object

Scopesthreads:readPages through results
Signature
Task<IReadOnlyList<JsonObject>> ListAllAsync(    string? folder = null,    string? query = null,    IEnumerable<string>? labelIds = null,    string? sort = null,    DateTimeOffset? dateFrom = null,    DateTimeOffset? dateTo = null,    bool? fromContacts = null,    bool? semantic = null,    string? category = null,    string? address = null,    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Walks every page with the same filters as ListAsync and returns once the last page is in, so the whole result sits in memory at once. That suits a label view or a small folder. For a large inbox, IterateAsync lets you stop as soon as you have what you need.

Each request asks for limit: threads, 25 when you leave it out, so raising it to 100 needs a quarter of the round trips. Passing cursor: starts the walk from that page instead of the first. The walk ends when the server stops offering a cursor or hands back the one it was given.

A failure on any page throws out of the call and discards everything collected so far.

Parameters

folderstring?

Label the threads must carry, case insensitive. Defaults to inbox.

querystring?

Mailbox search, as in ListAsync. Plain words must all appear and match loosely, a quoted phrase has to appear as written, filler words are dropped when something else is left to search for, and operators such as from:ada, has:pdf and in:anywhere narrow it.

labelIdsIEnumerable<string>?

Label ids a thread must all carry on top of folder:, matched exactly.

sortstring?

newest (the default), oldest, sender or subject, the four orders of the thread list in the app, as in Nextmail\Constants\ThreadSorts. sender and subject are alphabetical, newest first within one sender or subject.

dateFromDateTimeOffset?

Keeps threads whose newest message arrived at or after this instant. A DateTimeOffset.

dateToDateTimeOffset?

Keeps threads whose newest message arrived at or before this instant. Both ends are included, and dateFrom: after dateTo: is a 422.

fromContactsbool?

When true, keeps only threads whose newest message came from a saved contact. A key limited to some addresses reads the contacts its owner saved.

semanticbool?

When true, the plain words of query: match by meaning instead of spelling, in any language, and the closest threads come first along with every thread the words match as text. Operators and the other filters still apply and sort: does not. Ignored when the workspace has search by meaning turned off.

categorystring?

Keeps only the threads sorted into this inbox tab: primary, promotions, updates, social or forums. primary is every thread in no other tab. In query, category:promotions filters the same way in any folder.

addressstring?

Keeps only the threads delivered to this address, the address filter of the thread list. One the key does not reach returns nothing rather than failing.

limitint?

Page size for each request, a whole number from 1 to 100. Defaults to 25.

cursorstring?

A nextCursor to start the walk from instead of the first page.

apiKeystring?

Overrides the client's API key for every page of this walk.

cancellationTokenCancellationToken

Cancels the request.

Returns

A list of JsonObject items, every matching thread with object set to thread and its id, newest first.

Example

var snoozed = await client.Threads.ListAllAsync(folder: "snoozed", limit: 100); foreach (var summary in snoozed){    Console.WriteLine($"{summary["id"]} is waiting to wake");}

Notes

  • Each page is its own request with its own retries, so a network blip on page five does not restart the walk from page one.

  • Rows are ids only. Reading the threads afterwards is one GetAsync per id.

Also available in

API
GET /threads
TypeScript
nextmail.threads.listAll()
Python
threads.list_all()
Ruby
threads.list_all
PHP
threads->listAll
Go
Threads.ListAll
Java
threads().listAll

Threads.IterateAsync

Stream threads one at a time across pages

Scopesthreads:readPages through results
Signature
IAsyncEnumerable<JsonObject> IterateAsync(    string? folder = null,    string? query = null,    IEnumerable<string>? labelIds = null,    string? sort = null,    DateTimeOffset? dateFrom = null,    DateTimeOffset? dateTo = null,    bool? fromContacts = null,    bool? semantic = null,    string? category = null,    string? address = null,    int? limit = null,    string? cursor = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns an IAsyncEnumerable<JsonObject> that yields threads one by one and requests the next page only when the current one is used up. Nothing is fetched until the loop starts, and breaking out of it stops further requests, so this is the way to scan a large folder for the first match.

Filters behave as in ListAsync, and each request asks for limit: threads, 25 by default. The cursor marks a position in time rather than a row count, so trashing, archiving or relabelling threads inside the loop does not make the walk skip the ones after them. A thread that receives new mail during the walk moves ahead of the cursor and is not yielded again. With semantic: true the cursor counts places in the ranked list instead, so moving a thread out of the folder during the walk can shift the ones after it.

Parameters

folderstring?

Label the threads must carry, case insensitive. Defaults to inbox.

querystring?

Mailbox search, as in ListAsync. Plain words must all appear and match loosely, a quoted phrase has to appear as written, filler words are dropped when something else is left to search for, and operators such as from:ada, has:pdf and in:anywhere narrow it.

labelIdsIEnumerable<string>?

Label ids a thread must all carry on top of folder:, matched exactly.

sortstring?

newest (the default), oldest, sender or subject, the four orders of the thread list in the app, as in Nextmail\Constants\ThreadSorts. sender and subject are alphabetical, newest first within one sender or subject.

dateFromDateTimeOffset?

Keeps threads whose newest message arrived at or after this instant. A DateTimeOffset.

dateToDateTimeOffset?

Keeps threads whose newest message arrived at or before this instant. Both ends are included, and dateFrom: after dateTo: is a 422.

fromContactsbool?

When true, keeps only threads whose newest message came from a saved contact. A key limited to some addresses reads the contacts its owner saved.

semanticbool?

When true, the plain words of query: match by meaning instead of spelling, in any language, and the closest threads come first along with every thread the words match as text. Operators and the other filters still apply and sort: does not. Ignored when the workspace has search by meaning turned off.

categorystring?

Keeps only the threads sorted into this inbox tab: primary, promotions, updates, social or forums. primary is every thread in no other tab. In query, category:promotions filters the same way in any folder.

addressstring?

Keeps only the threads delivered to this address, the address filter of the thread list. One the key does not reach returns nothing rather than failing.

limitint?

Page size for each request, a whole number from 1 to 100. Defaults to 25.

cursorstring?

A nextCursor to start from instead of the first page.

apiKeystring?

Overrides the client's API key for every page of this walk.

cancellationTokenCancellationToken

Cancels the request.

Returns

An IAsyncEnumerable<JsonObject> that yields one object per thread, each with object set to thread and id.

Example

await foreach (var summary in client.Threads.IterateAsync(folder: "inbox", query: "receipt newer_than:30d", limit: 100)){    var thread = await client.Threads.GetAsync(summary["id"]!.GetValue<string>());     if ((bool?)thread["hasUnread"] == true)    {        await client.Threads.UpdateAsync(summary["id"]!.GetValue<string>(), new Body { ["read"] = true });    }}

Notes

  • The generator is lazy, so an abandoned loop costs only the pages it consumed.

Also available in

API
GET /threads
TypeScript
nextmail.threads.iterate()
Python
threads.iterate()
Ruby
threads.iterate
PHP
threads->iterate
Go
Threads.Iterate
Java
threads().iterate

Threads.GetAsync

Read a thread with every message on it

Scopesthreads:read
Signature
Task<JsonObject> GetAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns the whole conversation, oldest message first, with the labels the thread sits in and its unread state. Unsent draft replies are included in messages with isDraft: true, which is why messageCount, the length of messages, can be higher than totalReplies, which counts only real messages.

Messages are passed through as the mailbox stored them, so each message is an open object rather than a fixed field list. The one field the API commits to is encryption. When encryption.format is pgp-mime, pgp-inline or smime-encrypted, the message is sealed and its body is empty or holds armour, which NextmailClient.IsSealed(message) checks for you. pgp-signed and smime-signed are ordinary readable mail. A message with no encryption at all was stored before detection existed, so its absence says nothing about whether it was plaintext.

Each message's tags and unread are overwritten on read with the thread's current labels and unread flag, so they describe the thread and not that one message.

Parameters

idstringRequired

Thread id, as returned by ListAsync or carried on a message.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with id echoed from the request, messages, labels as objects with id, name and color, messageCount, hasUnread, totalReplies and deliveredTo, the workspace address the thread belongs to (null when none was recorded).

Example

var thread = await client.Threads.GetAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"{string.Join(", ", (thread["labels"]?.AsArray() ?? []).Select(row => row?["name"]))}");

Notes

  • A thread delivered to no address the key covers is a 404, exactly like one that does not exist.

  • hasUnread mirrors the UNREAD label, and UpdateAsync with read is how you change it.

  • Draft ids from Drafts.ListAsync open here too, because a draft is stored as a thread labelled DRAFT.

Also available in

API
GET /threads/{id}
TypeScript
nextmail.threads.get()
Python
threads.get()
Ruby
threads.get
PHP
threads->get
Go
Threads.Get
Java
threads().get
CLI
nextmail threads get

Threads.UpdateAsync

Mark a thread read or unread and change its labels

Scopesthreads:write
Signature
Task<JsonObject> UpdateAsync(    string id,    IReadOnlyDictionary<string, object?> patch,    string? apiKey = null,    CancellationToken cancellationToken = default)

Adds and removes labels on a thread in one call. read is shorthand for the UNREAD label: true removes it and false adds it. At least one of read, a non empty addLabelIds or a non empty removeLabelIds is required, each list takes at most 50 ids, and any other key is a 422 because the body is strict.

Folders are labels too, so archiving is ["addLabelIds"] = new[] { "ARCHIVE" } with ["removeLabelIds"] = new[] { "INBOX" }, the same pair the app uses. TRASH, SNOOZED and DRAFT are refused in either list with 422 label_not_directly_settable, because each needs a step this route cannot take. Use TrashAsync and SnoozeAsync for those.

User label ids come from Labels.ListAsync, and the system ids such as ARCHIVE, STARRED and UNREAD are taken in any case. An id in addLabelIds that names no label is refused with 422 label_not_found and nothing on the thread changes, so create the label with Labels.CreateAsync first. An unknown id in removeLabelIds is not an error, since the thread cannot carry it. Removals are applied before additions, so an id in both lists ends up on the thread.

Parameters

idstringRequired

Thread id.

readbool

true removes UNREAD, false adds it.

addLabelIdsIEnumerable<string>

Label ids to put on the thread, at most 50, each naming a label, never TRASH, SNOOZED or DRAFT.

removeLabelIdsIEnumerable<string>

Label ids to take off the thread, at most 50, never TRASH, SNOOZED or DRAFT.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with id, addedLabelIds and removedLabelIds. Both lists echo the request, with system ids upper cased, plus the UNREAD change that read implies, not what actually changed.

Example

var updated = await client.Threads.UpdateAsync("CAHk7pQ2x9LmZ4-mail.example.com", new Body{    ["read"] = true,    ["addLabelIds"] = new[] { "ARCHIVE", "USER_RECEIPTS" },    ["removeLabelIds"] = new[] { "INBOX" },}); Console.WriteLine($"Added {string.Join(", ", updated["addedLabelIds"]?.AsArray() ?? [])}");Console.WriteLine($"Removed {string.Join(", ", updated["removedLabelIds"]?.AsArray() ?? [])}");

Notes

  • The SDK retries this call after a network failure or a retryable status, which is safe because adding a label already present or removing one already gone changes nothing.

  • The thread is looked up before the body is validated, so a wrong id is a 404 even when the patch is also invalid.

  • User label ids look like USER_RECEIPTS. Take them from Labels.ListAsync rather than building them.

Also available in

API
PATCH /threads/{id}
TypeScript
nextmail.threads.update()
Python
threads.update()
Ruby
threads.update
PHP
threads->update
Go
Threads.Update
Java
threads().update
CLI
nextmail threads update

Threads.TrashAsync

Move a thread to the Bin

Scopesthreads:write
Signature
Task<JsonObject> TrashAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Adds TRASH and removes INBOX, SPAM, SNOOZED and ARCHIVE in one step, which is what the app's delete does. Doing half of that through UpdateAsync would leave the thread listed in both the Bin and its old folder, which is why UpdateAsync refuses TRASH.

Nothing is deleted. The thread stays readable with GetAsync and lists under folder: 'trash'. Calling this on a thread that is already in the Bin changes nothing and returns the same body, which is why the SDK retries it.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to thread, id and trashed set to true.

Example

var result = await client.Threads.TrashAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"{result["id"]}{((bool?)result["trashed"] == true ? " is in the Bin" : " was not moved")}");

Notes

  • RestoreAsync takes a thread back out of the Bin. UpdateAsync refuses TRASH in removeLabelIds as well as in addLabelIds.

  • Trashing a snoozed thread also cancels its scheduled wake, so it does not reappear in the inbox later.

  • A thread delivered to no address the key covers is a 404.

Also available in

API
POST /threads/{id}/trash
TypeScript
nextmail.threads.trash()
Python
threads.trash()
Ruby
threads.trash
PHP
threads->trash
Go
Threads.Trash
Java
threads().trash
CLI
nextmail threads trash

Threads.SnoozeAsync

Hide a thread until a set time

Scopesthreads:write
Signature
Task<JsonObject> SnoozeAsync(    string id,    DateTimeOffset wakeAt,    string? apiKey = null,    CancellationToken cancellationToken = default)

Adds SNOOZED, removes INBOX and stores a wake time, all in one call. Both halves matter: the label hides the thread and the stored wake time is what brings it back. A thread labelled SNOOZED any other way would never return, which is why UpdateAsync refuses that label.

wakeAt takes a DateTimeOffset or an ISO 8601 string. The SDK sends a DateTimeOffset as an ISO 8601 instant in UTC, and the server answers 422 invalid_parameter on wakeAt when the value does not parse or is not in the future. Snoozing a thread that is already snoozed replaces its wake time.

Threads are woken by an hourly sweep, so one comes back at the first sweep after wakeAt, up to about an hour late. Opening the Snoozed folder in the app wakes overdue threads straight away. A thread always wakes into the inbox.

Parameters

idstringRequired

Thread id.

wakeAtDateTimeOffsetRequired

When the thread should return, a future instant.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with id and snoozedUntil, the wake time normalised to a UTC ISO 8601 string.

Example

var london = TimeZoneInfo.FindSystemTimeZoneById("Europe/London");var tomorrow = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, london).Date.AddDays(1).AddHours(9); var snoozed = await client.Threads.SnoozeAsync("CAHk7pQ2x9LmZ4-mail.example.com", new DateTimeOffset(tomorrow, london.GetUtcOffset(tomorrow))); Console.WriteLine($"Back at {snoozed["snoozedUntil"]}");

Notes

  • Only INBOX is removed, so a thread snoozed from another folder keeps that folder's label while it sleeps and wakes carrying both.

  • A string with no zone offset is read in the server's local zone, so send Z or an explicit offset.

  • The SDK retries this call, which is safe because a repeat stores the same wake time.

Also available in

API
POST /threads/{id}/snooze
TypeScript
nextmail.threads.snooze()
Python
threads.snooze()
Ruby
threads.snooze
PHP
threads->snooze
Go
Threads.Snooze
Java
threads().snooze
CLI
nextmail threads snooze

Threads.UnsnoozeAsync

Bring a snoozed thread back now

Scopesthreads:write
Signature
Task<JsonObject> UnsnoozeAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Adds INBOX, removes SNOOZED and deletes the stored wake time, so the thread returns immediately and the hourly sweep leaves it alone afterwards.

A thread that is not snoozed is left where it is, so an archived thread stays archived, and the response still reports snoozedUntil as null.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with id and snoozedUntil set to null.

Example

var result = await client.Threads.UnsnoozeAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"{result["id"]} is back in the inbox");

Notes

  • Removing SNOOZED through UpdateAsync is refused with 422 label_not_directly_settable, because it would leave the wake time scheduled.

  • Safe to retry, and the SDK does: a second call applies the same labels and deletes a wake time that is already gone.

Also available in

API
POST /threads/{id}/unsnooze
TypeScript
nextmail.threads.unsnooze()
Python
threads.unsnooze()
Ruby
threads.unsnooze
PHP
threads->unsnooze
Go
Threads.Unsnooze
Java
threads().unsnooze
CLI
nextmail threads unsnooze

Threads.MuteAsync

Mute a thread

Scopesthreads:write
Signature
Task<JsonObject> MuteAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

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, which is why the SDK retries it. UpdateAsync with MUTE in addLabelIds does the same.

Parameters

idstringRequired

Thread id.

cancellationTokenCancellationToken

Cancels the request.

apiKeystring?

Overrides the client's API key for this call only.

Returns

A JsonObject, { object: 'thread', id, muted: true }.

Example

var muted = await client.Threads.MuteAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"{muted["id"]} muted: {muted["muted"]}");

Notes

  • Only a thread in the inbox moves. One in Sent, the archive or Snoozed stays where it is.

  • A muted thread carries the built-in MUTE label, which GetAsync shows among its labels and is:muted finds in query.

  • UnmuteAsync takes the mute off. A thread delivered to no address the key covers is a 404.

Also available in

API
POST /threads/{id}/mute
TypeScript
nextmail.threads.mute()
Python
threads.mute()
Ruby
threads.mute
PHP
threads->mute
Go
Threads.Mute
Java
threads().mute
CLI
nextmail threads mute

Threads.UnmuteAsync

Take the mute off a thread

Scopesthreads:write
Signature
Task<JsonObject> UnmuteAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

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 UpdateAsync and INBOX in addLabelIds if you want it in the inbox now. A thread that is not muted is left as it is, which is why the SDK retries this call.

Parameters

idstringRequired

Thread id.

cancellationTokenCancellationToken

Cancels the request.

apiKeystring?

Overrides the client's API key for this call only.

Returns

A JsonObject, { object: 'thread', id, muted: false }.

Example

var thread = await client.Threads.UnmuteAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"{thread["id"]} muted: {thread["muted"]}"); await client.Threads.UpdateAsync("CAHk7pQ2x9LmZ4-mail.example.com", new Body { ["addLabelIds"] = new[] { "INBOX" } });

Notes

  • Unmuting is for the whole workspace, as muting is: the thread alerts everyone on it again.

  • A thread delivered to no address the key covers is a 404.

Also available in

API
POST /threads/{id}/unmute
TypeScript
nextmail.threads.unmute()
Python
threads.unmute()
Ruby
threads.unmute
PHP
threads->unmute
Go
Threads.Unmute
Java
threads().unmute
CLI
nextmail threads unmute

Threads.ListMessagesAsync

List the messages of a thread in a fixed shape

Scopesthreads:read
Signature
Task<IReadOnlyList<JsonObject>> ListMessagesAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns every message on the thread, oldest first, each as a JsonObject in one fixed shape: from, to, cc, bcc and replyTo with a name and an email each, date as an ISO 8601 instant, messageId, inReplyTo and references, the body as text and html, the attachments without their bytes, the headers Nextmail keeps and encryption. Unsent draft replies are in the list with draft set to true.

These fields are a contract: they are added to, never renamed or taken away, whatever the mailbox stores underneath. GetAsync still returns each message the way the mailbox stored it, so use this when your code reads messages field by field.

text is the plain text body, or the text drawn from the HTML when the message came as HTML only. html is null for a plain text message. headers holds From, To, Cc, Reply-To, Subject, Date, Message-ID, In-Reply-To, References, List-Unsubscribe and List-Unsubscribe-Post when the message had them, and nothing else, because the rest of what it arrived with is not kept.

Parameters

idstringRequired

Thread id, as returned by ListAsync.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A list of JsonObject, oldest first.

Example

var messages = await client.Threads.ListMessagesAsync("thr_8f2c41d0a3b94e6f"); foreach (var message in messages){    Console.WriteLine($"{message["date"]} {message["from"]?["email"]} {message["subject"]}");}

Notes

  • A thread delivered to no address the key covers is a 404, exactly like one that does not exist.

  • Read an attachment's bytes with ListAttachmentsAsync(id, messageId).

Also available in

API
GET /threads/{id}/messages
TypeScript
nextmail.threads.listMessages()
Python
threads.list_messages()
Ruby
threads.list_messages
PHP
threads->listMessages
Go
Threads.ListMessages
Java
threads().listMessages
CLI
nextmail threads list-messages

Threads.GetMessageAsync

Read one message of a thread in a fixed shape

Scopesthreads:read
Signature
Task<JsonObject> GetMessageAsync(    string id,    string messageId,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns one message as a JsonObject in the shape ListMessagesAsync lists. The thread is checked first and then the message, so a messageId that is not on that thread is a 404 even when it exists elsewhere in the mailbox.

The emailId of an email.received webhook is the messageId to pass here, beside its threadId.

Parameters

idstringRequired

Thread id the message belongs to.

messageIdstringRequired

The message, as id on a message ListMessagesAsync returns.

apiKeystring?

Overrides the client API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with the fields ListMessagesAsync returns for each message.

Example

var message = await client.Threads.GetMessageAsync("thr_8f2c41d0a3b94e6f", "msg_3f9a1c07d2b84e6a9c5b1f20"); Console.WriteLine($"{message["from"]?["email"]} {message["date"]}");Console.WriteLine(message["attachments"]?.AsArray().Count);

Notes

  • Both ids must match: a real message id paired with the wrong thread id is a 404 resource_not_found.

  • A thread delivered to no address the key covers is a 404 before the message is looked at.

Also available in

API
GET /threads/{id}/messages/{messageId}
TypeScript
nextmail.threads.getMessage()
Python
threads.get_message()
Ruby
threads.get_message
PHP
threads->getMessage
Go
Threads.GetMessage
Java
threads().getMessage
CLI
nextmail threads get-message

Threads.ListAttachmentsAsync

List a message's attachments with their content

Scopesthreads:read
Signature
Task<IReadOnlyList<JsonObject>> ListAttachmentsAsync(    string id,    string messageId,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns the attachments of one message with each file's bytes inlined as base64 in content. The thread is checked first and then the message, so a messageId that is not on that thread is a 404 even when it exists elsewhere in the mailbox. Take message ids from the messages of Threads.GetAsync.

This is the display list. The ciphertext of an encrypted envelope is in it and downloads like any other file, named encrypted-message.asc when it arrived without a name. The PGP/MIME version part and any detached signature are held out on purpose. Every part keeps its id in the message's encryption.parts, and for those two the id is a correlation key only: no route returns their bytes.

Every file comes back whole in a single response, with no size cap and no range reads, so a message carrying large files makes a large response.

Parameters

idstringRequired

Thread id the message belongs to.

messageIdstringRequired

Message id from that thread's messages.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A list of JsonObject items, each with attachmentId, filename, contentType, size and base64 content.

Example

var files = await client.Threads.ListAttachmentsAsync("CAHk7pQ2x9LmZ4-mail.example.com", "message_4c1b257a"); Console.WriteLine(files.Count);

Notes

  • When the stored bytes for a file cannot be found, content is an empty string rather than null, so check its length before decoding.

  • Both ids must match: a real message id paired with the wrong thread id is a 404 resource_not_found.

  • A thread delivered to no address the key covers is a 404 before the message is looked at.

Also available in

API
GET /threads/{id}/messages/{messageId}/attachments
TypeScript
nextmail.threads.listAttachments()
Python
threads.list_attachments()
Ruby
threads.list_attachments
PHP
threads->listAttachments
Go
Threads.ListAttachments
Java
threads().listAttachments
CLI
nextmail threads list-attachments

Threads.ListNotesAsync

List the notes on a thread

Scopesthreads:read
Signature
Task<IReadOnlyList<JsonObject>> ListNotesAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns every note on one thread, the Notes panel of the reading pane: pinned notes first, then the rest in the order they were arranged.

Notes are private to a person. A key reads and writes the notes of the workspace owner, and an app those of the person who connected it. A key limited to particular addresses reaches only the notes on threads that arrived at them.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A list of JsonObject items, each with id, threadId, content, color, pinned, order, createdAt and updatedAt.

Example

var notes = await client.Threads.ListNotesAsync("CAHk7pQ2x9LmZ4-mail.example.com"); foreach (var note in notes){    Console.WriteLine($"{((bool?)note["pinned"] == true ? "Pinned: " : "")}{note["content"]}");}

Notes

  • A thread delivered to no address the key covers is a 404.

Also available in

API
GET /threads/{id}/notes
TypeScript
nextmail.threads.listNotes()
Python
threads.list_notes()
Ruby
threads.list_notes
PHP
threads->listNotes
Go
Threads.ListNotes
Java
threads().listNotes
CLI
nextmail threads list-notes

Threads.CreateNoteAsync

Add a note to a thread

Scopesthreads:write
Signature
Task<JsonObject> CreateNoteAsync(    string id,    IReadOnlyDictionary<string, object?> body,    string? apiKey = null,    CancellationToken cancellationToken = default)

Pins a private note to a thread, as the Notes panel does. A new note goes after the others, and pinned keeps it at the top.

Notes are private to a person. A key reads and writes the notes of the workspace owner, and an app those of the person who connected it. A key limited to particular addresses reaches only the notes on threads that arrived at them.

Parameters

idstringRequired

Thread id.

contentstringRequired

The text of the note, up to 20,000 characters. Leading and trailing spaces are trimmed.

colorstring

One of the eight the app offers: default, red, orange, yellow, green, blue, purple or pink, as in Nextmail\Constants\ThreadNoteColors. Defaults to default.

pinnedbool

Keeps the note above the others. Defaults to false.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject for the new note, with the fields ListNotesAsync returns.

Example

using Nextmail.Constants; var note = await client.Threads.CreateNoteAsync("CAHk7pQ2x9LmZ4-mail.example.com", new Body{    ["content"] = "Waiting on the signed contract before replying.",    ["color"] = ThreadNoteColors.Yellow,    ["pinned"] = true,}); Console.WriteLine($"{note["id"]}");

Notes

  • The SDK does not retry it, because a second call would add a second note.

Also available in

API
POST /threads/{id}/notes
TypeScript
nextmail.threads.createNote()
Python
threads.create_note()
Ruby
threads.create_note
PHP
threads->createNote
Go
Threads.CreateNote
Java
threads().createNote
CLI
nextmail threads create-note

Threads.UpdateNoteAsync

Change a note

Scopesthreads:write
Signature
Task<JsonObject> UpdateNoteAsync(    string id,    string noteId,    IReadOnlyDictionary<string, object?> patch,    string? apiKey = null,    CancellationToken cancellationToken = default)

Changes the text of a note, its colour or whether it is pinned. Give at least one of the three. A note id that is not on this thread is a 404, even when the note exists on another thread.

Parameters

idstringRequired

Thread id.

noteIdstringRequired

The note's id, from ListNotesAsync.

contentstring

The new text, up to 20,000 characters.

colorstring

The new colour, one of Nextmail\Constants\ThreadNoteColors.

pinnedbool

Pins or unpins it.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject for the note as it is now, with the fields ListNotesAsync returns.

Example

var note = await client.Threads.UpdateNoteAsync("CAHk7pQ2x9LmZ4-mail.example.com", "b3d1f0c2-7a4e-4f7b-9c1d-2e8f6a5b4c3d", new Body { ["pinned"] = false }); Console.WriteLine($"{((bool?)note["pinned"] == true ? "Still pinned" : "Unpinned")}");

Notes

  • Safe to repeat: setting the same values twice leaves the note as it was.

Also available in

API
PATCH /threads/{id}/notes/{noteId}
TypeScript
nextmail.threads.updateNote()
Python
threads.update_note()
Ruby
threads.update_note
PHP
threads->updateNote
Go
Threads.UpdateNote
Java
threads().updateNote
CLI
nextmail threads update-note

Threads.DeleteNoteAsync

Delete a note

Scopesthreads:write
Signature
Task<JsonObject> DeleteNoteAsync(    string id,    string noteId,    string? apiKey = null,    CancellationToken cancellationToken = default)

Deletes a note for good. There is no bin for notes.

Parameters

idstringRequired

Thread id.

noteIdstringRequired

The note's id, from ListNotesAsync.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to note, id, threadId and deleted set to true.

Example

var deleted = await client.Threads.DeleteNoteAsync("CAHk7pQ2x9LmZ4-mail.example.com", "b3d1f0c2-7a4e-4f7b-9c1d-2e8f6a5b4c3d"); Console.WriteLine($"{deleted["id"]} deleted");

Notes

  • The SDK does not retry a delete. A 404 on your own second attempt after a lost response means the first one worked.

Also available in

API
DELETE /threads/{id}/notes/{noteId}
TypeScript
nextmail.threads.deleteNote()
Python
threads.delete_note()
Ruby
threads.delete_note
PHP
threads->deleteNote
Go
Threads.DeleteNote
Java
threads().deleteNote
CLI
nextmail threads delete-note

Threads.ReorderNotesAsync

Arrange the notes on a thread

Scopesthreads:write
Signature
Task<IReadOnlyList<JsonObject>> ReorderNotesAsync(    string id,    IEnumerable<string> ids,    string? apiKey = null,    CancellationToken cancellationToken = default)

Sets the order of every note on a thread at once, first to last, as dragging them in the Notes panel does. Pinned notes still come first.

ids has to name every note on the thread exactly once. Anything else is a 422 invalid_parameter on ids, and nothing moves.

Parameters

idstringRequired

Thread id.

idsIEnumerable<string>Required

Every note id on the thread, in the order you want them.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A list of note objects in the new order.

Example

var notes = await client.Threads.ListNotesAsync("CAHk7pQ2x9LmZ4-mail.example.com");var ids = notes.Select(note => note["id"]!.GetValue<string>()).Reverse(); var reordered = await client.Threads.ReorderNotesAsync("CAHk7pQ2x9LmZ4-mail.example.com", ids); foreach (var note in reordered){    Console.WriteLine($"{note["order"]} {note["content"]}");}

Notes

  • Safe to repeat: the same order twice leaves the notes as they were.

Also available in

API
POST /threads/{id}/notes/reorder
TypeScript
nextmail.threads.reorderNotes()
Python
threads.reorder_notes()
Ruby
threads.reorder_notes
PHP
threads->reorderNotes
Go
Threads.ReorderNotes
Java
threads().reorderNotes
CLI
nextmail threads reorder-notes

Threads.CountsAsync

Count the mail in each folder

Scopesthreads:read
Signature
Task<JsonObject> CountsAsync(    string? address = null,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns 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 inbox tab, and how many inbox conversations arrived at each address of the workspace.

folders has one row per folder, named by its label id in lower case: inbox, sent, spam, archive, trash and snoozed, then draft, whose count is the drafts waiting to be sent and whose unread is always 0, then unread, whose count is the unread conversations in the inbox. labels has one row for each of your labels and each folder it has conversations in, with id, folder, count and unread, so a label with nothing in a folder has no row for it. categories has one row for each inbox tab, with category, count and unread, counting inbox conversations only: primary, promotions, updates, social and forums, where primary is everything not sorted into another tab. unlabelled counts the inbox conversations that carry none of your labels, with count, unread and the same count for each tab under categories, which is what the inbox of the app lists unless Labelled mail is ticked in its Filters menu. addresses counts the inbox per address the mail was delivered to, with null for mail that recorded none. team counts the team views of a shared inbox for the person the key or app acts for: assigned the open conversations assigned to them, unassigned the open conversations nobody has, each with count and unread, and mentions the conversations outside Spam and the bin where a team comment mentions them, with count, unread and unseen, the conversations with a mention newer than the last time they opened its comments in the app. A conversation is open while it is in the inbox.

A key limited to particular addresses counts only the mail that arrived at them and the drafts written from them. address: narrows every count to one address, and one the key does not reach counts nothing rather than failing.

Parameters

addressstring?

Count only the mail delivered to this address.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to mailbox_counts, folders, addresses, labels, categories, unlabelled and team. Each folder is an object with label, count and unread, each address an object with address and count, each label an object with id, folder, count and unread, and each category an object with category, count and unread. unlabelled is an object with count, unread and categories for the inbox conversations with no label. team is an object with assigned, unassigned and mentions, each an object with count and unread, and mentions also has unseen.

Example

var counts = await client.Threads.CountsAsync(); foreach (var folder in counts["folders"]?.AsArray() ?? []){    Console.WriteLine($"{folder?["label"]}: {folder?["unread"]} unread of {folder?["count"]}");} foreach (var row in counts["addresses"]?.AsArray() ?? []){    Console.WriteLine($"{row?["address"]?.ToString() ?? "(none)"}: {row?["count"]}");} Console.WriteLine($"{counts["team"]?["unassigned"]?["count"]} open and nobody has them");

Notes

  • Read only, so the SDK retries it after a network failure like any other read.

Also available in

API
GET /threads/counts
TypeScript
nextmail.threads.counts()
Python
threads.counts()
Ruby
threads.counts
PHP
threads->counts
Go
Threads.Counts
Java
threads().counts
CLI
nextmail threads counts

Threads.SummaryAsync

Read the summary of a thread

Scopesthreads:read
Signature
Task<JsonObject> SummaryAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns the short AI summary the reading pane shows above a thread. A summary 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 one is cheap.

state is ready with the text in summary, pending while the first one is being written, failed when the last try did not work, or none when there is nothing to summarise, such as a thread holding a message that arrived encrypted, whose body Nextmail never reads. While a newer summary is being written, the one before it comes back as ready. A pending answer starts the writing, so ask again a few seconds later, and after a failed one asking again tries once more.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to thread_summary, threadId, state and summary, with summary null unless state is ready.

Example

var result = await client.Threads.SummaryAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"{result["summary"]?.ToString() ?? "Nothing to summarise"}");

Notes

  • Summaries spend no AI actions, whether one is written or only read.

  • A thread delivered to no address the key covers is a 404.

Also available in

API
GET /threads/{id}/summary
TypeScript
nextmail.threads.summary()
Python
threads.summary()
Ruby
threads.summary
PHP
threads->summary
Go
Threads.Summary
Java
threads().summary
CLI
nextmail threads summary

Threads.ReplySuggestionsAsync

Suggest replies to a thread

Scopesthreads:read
Signature
Task<JsonObject> ReplySuggestionsAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns up to three 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 in plain text, ready to send with Emails.SendAsync or to keep with Drafts.CreateAsync. 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 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, so ask again a few seconds later, 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. Every thread is none in a workspace that turned ReplySuggestionsAsync off with Settings.UpdateAsync.

sources names the knowledge base items the suggestions drew on: the pinned notes at the levels of the receiving address and the items whose passages matched the message, each with id, title, kind, scope and url. It is empty when none were used.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to reply_suggestions, threadId, messageId, state, suggestions and sources, with messageId naming the message they answer, suggestions empty unless state is ready and sources the knowledge base items they drew on. Each suggestion is an object with label and body.

Example

var result = await client.Threads.ReplySuggestionsAsync("CAHk7pQ2x9LmZ4-mail.example.com"); foreach (var suggestion in result["suggestions"]?.AsArray() ?? []){    Console.WriteLine($"{suggestion?["label"]}: {suggestion?["body"]}");}

Notes

  • Writing suggestions spends none of the workspace's AI actions. A workspace has suggestions written for a limited number of new messages a day, and past it a thread reads none until the next day.

  • A thread delivered to no address the key covers is a 404.

  • A server without AI answers 409 ai_not_configured.

Also available in

API
GET /threads/{id}/reply-suggestions
TypeScript
nextmail.threads.replySuggestions()
Python
threads.reply_suggestions()
Ruby
threads.reply_suggestions
PHP
threads->replySuggestions
Go
Threads.ReplySuggestions
Java
threads().replySuggestions
CLI
nextmail threads reply-suggestions

Threads.RestoreAsync

Take a thread out of the Bin

Scopesthreads:write
Signature
Task<JsonObject> RestoreAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

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 TrashAsync.

Calling it on a thread that is already in the inbox changes nothing and returns the same body, which is why the SDK retries it.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to thread, id and restored set to true.

Example

await client.Threads.TrashAsync("CAHk7pQ2x9LmZ4-mail.example.com"); var restored = await client.Threads.RestoreAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"{((bool?)restored["restored"] == true ? "Back in the inbox" : "Not restored")}");

Notes

  • A thread that was archived before it went to the Bin comes back to the inbox, not to the archive.

  • A thread delivered to no address the key covers is a 404.

Also available in

API
POST /threads/{id}/restore
TypeScript
nextmail.threads.restore()
Python
threads.restore()
Ruby
threads.restore
PHP
threads->restore
Go
Threads.Restore
Java
threads().restore
CLI
nextmail threads restore

Threads.DeleteAsync

Delete a thread for good

Scopesthreads:write
Signature
Task<JsonObject> DeleteAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Deletes every message in a thread, with its attachments, which is what Delete from Bin does in the app. It cannot be undone.

It works on a thread in any folder, so call TrashAsync instead when you only mean to discard the thread: a trashed thread stays readable and RestoreAsync brings it back.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to thread, id and deleted set to true.

Example

var deleted = await client.Threads.DeleteAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"{deleted["id"]} is gone for good");

Notes

  • The SDK does not retry a delete. A 404 on your own second attempt after a lost response means the first one worked.

  • A 500 thread_delete_failed means nothing was removed, so the call is safe to repeat.

  • A thread delivered to no address the key covers is a 404.

Also available in

API
DELETE /threads/{id}
TypeScript
nextmail.threads.delete()
Python
threads.delete()
Ruby
threads.delete
PHP
threads->delete
Go
Threads.Delete
Java
threads().delete
CLI
nextmail threads delete

Threads.UnsubscribeAsync

Unsubscribe from the sender of a thread

Scopesthreads:write
Signature
Task<JsonObject> UnsubscribeAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Does what the Unsubscribe button of the reading pane does: finds the subscription behind the newest message of the thread that carries a List-Unsubscribe header and unsubscribes from it the way the sender asks for, with a one-click request or an unsubscribe email. A sender that only offers a page cannot be unsubscribed by a program, so method is link and url is the page a person has to open.

A thread with no unsubscribe header is a 422 unsubscribe_unsupported, and a thread delivered to no address the key covers is a 404.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to unsubscribe, threadId, subscriptionId, method, url and binned.

Example

using Nextmail.Constants; var result = await client.Threads.UnsubscribeAsync("CAHk7pQ2x9LmZ4-mail.example.com"); if ((string?)result["method"] == UnsubscribeMethods.Link){    Console.WriteLine($"Open this page to finish: {result["url"]}");}else{    Console.WriteLine($"Unsubscribed by {result["method"]}");}

Notes

  • The SDK does not retry it, because a second call can send a second unsubscribe email.

Also available in

API
POST /threads/{id}/unsubscribe
TypeScript
nextmail.threads.unsubscribe()
Python
threads.unsubscribe()
Ruby
threads.unsubscribe
PHP
threads->unsubscribe
Go
Threads.Unsubscribe
Java
threads().unsubscribe
CLI
nextmail threads unsubscribe

Threads.GetEventAsync

Read the event a thread carries

Scopescalendar:read
Signature
Task<JsonObject> GetEventAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns the calendar event that came with an invitation in the thread, as the invitation card of the reading pane shows it. Answer it with Calendar.RespondToEventAsync.

Parameters

idstringRequired

Thread id, as Threads.ListAsync returns it.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject, the event as Calendar.GetEventAsync returns it.

Example

var entry = await client.Threads.GetEventAsync("thr_6d1a9c4e2b7f30d85e1c4a92"); Console.WriteLine($"{entry["summary"]} at {entry["start"]}");

Notes

  • A thread with no event, or one the key does not reach, is a 404.

Also available in

API
GET /threads/{id}/event
TypeScript
nextmail.threads.getEvent()
Python
threads.get_event()
Ruby
threads.get_event
PHP
threads->getEvent
Go
Threads.GetEvent
Java
threads().getEvent
CLI
nextmail threads get-event

Threads.ListSenderCategoriesAsync

List the senders that always go to one inbox tab

Scopesthreads:read
Signature
Task<IReadOnlyList<JsonObject>> ListSenderCategoriesAsync(    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns 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. ListAsync takes category to read one tab.

The choices belong to the workspace and apply to every address in it. A workspace holds at most 2,000 of them, and they come back as one plain list with no paging.

Parameters

cancellationTokenCancellationToken

Cancels the request.

apiKeystring?

Overrides the client's API key for this call only.

Returns

A list of JsonObject items, each { object: 'sender_category', sender, category, updatedAt }, with sender in lower case and category the tab their mail goes to.

Example

var choices = await client.Threads.ListSenderCategoriesAsync(); foreach (var choice in choices){    Console.WriteLine($"{choice["sender"]} always goes to {choice["category"]}");}

Notes

  • A key or an app limited to particular addresses or domains can read the choices but not change them.

  • Read only, so the SDK retries it after a network failure like any other read.

Also available in

API
GET /sender-categories
TypeScript
nextmail.threads.listSenderCategories()
Python
threads.list_sender_categories()
Ruby
threads.list_sender_categories
PHP
threads->listSenderCategories
Go
Threads.ListSenderCategories
Java
threads().listSenderCategories
CLI
nextmail threads list-sender-categories

Threads.SetSenderCategoryAsync

Always sort a sender into one inbox tab

Scopesthreads:write
Signature
Task<JsonObject> SetSenderCategoryAsync(    string email,    string category,    string? apiKey = null,    CancellationToken cancellationToken = default)

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 the call past that is a 422 sender_category_limit_reached. They apply to every address in the workspace, so a key or an app limited to particular addresses or domains can read them but not change them, and gets a 422 capability_unsupported.

Parameters

emailstringRequired

The address of the sender, compared without case.

categorystringRequired

The inbox tab their mail goes to: primary, promotions, updates, social or forums.

cancellationTokenCancellationToken

Cancels the request.

apiKeystring?

Overrides the client's API key for this call only.

Returns

A JsonObject, { object: 'sender_category', sender, category, updatedAt }, with sender in lower case.

Example

using Nextmail.Constants; var choice = await client.Threads.SetSenderCategoryAsync("news@acme.com", InboxCategories.Primary); Console.WriteLine($"{choice["sender"]} goes to {choice["category"]} from now on");

Notes

  • An address or a category that is not valid is a 422 invalid_parameter.

  • Choosing primary keeps a sender out of the other tabs, which is how to stop a newsletter you read from being sorted away.

  • The SDK retries this call after a network failure, which is safe because saving the same choice twice leaves the same choice.

Also available in

API
PUT /sender-categories/{email}
TypeScript
nextmail.threads.setSenderCategory()
Python
threads.set_sender_category()
Ruby
threads.set_sender_category
PHP
threads->setSenderCategory
Go
Threads.SetSenderCategory
Java
threads().setSenderCategory
CLI
nextmail threads set-sender-category

Threads.ClearSenderCategoryAsync

Remove the tab chosen for a sender

Scopesthreads:write
Signature
Task<JsonObject> ClearSenderCategoryAsync(    string email,    string? apiKey = null,    CancellationToken cancellationToken = default)

Removes the choice, so new mail from the sender is sorted by the rules again. Threads already sorted stay where they are.

Parameters

emailstringRequired

The address of the sender, compared without case.

cancellationTokenCancellationToken

Cancels the request.

apiKeystring?

Overrides the client's API key for this call only.

Returns

A JsonObject, { object: 'sender_category', sender, deleted: true }, with sender in lower case.

Example

try{    await client.Threads.ClearSenderCategoryAsync("news@acme.com");}catch (NextmailApiException error) when (error.IsNotFound){    Console.WriteLine("No tab was chosen for that sender");}

Notes

  • A sender with no saved choice is a 404. The SDK does not retry a delete, so a 404 on your own second attempt after a lost response means the first one worked.

  • A key or an app limited to particular addresses or domains cannot change the choices, and gets a 422 capability_unsupported.

Also available in

API
DELETE /sender-categories/{email}
TypeScript
nextmail.threads.clearSenderCategory()
Python
threads.clear_sender_category()
Ruby
threads.clear_sender_category
PHP
threads->clearSenderCategory
Go
Threads.ClearSenderCategory
Java
threads().clearSenderCategory
CLI
nextmail threads clear-sender-category

Threads.GetTeamAsync

Read who has a thread

Scopesthreads:read
Signature
Task<JsonObject> GetTeamAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns the thread as the team sees it: who it is assigned to, who marked it done, how many comments it has, who else is writing a reply in the app right now, and everyone who can see it.

Only people who can see a thread can be given it or mentioned on it: the workspace owner, members who reach every address, and members who hold its address or its domain. A key acts for the workspace owner.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to thread_team, threadId, assignment, commentCount, replying and team, where each person has id, name and email.

Example

var team = await client.Threads.GetTeamAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"{team["assignment"]?["assignee"]?["email"]?.ToString() ?? "nobody"}, {team["commentCount"]} comments");

Notes

  • A thread delivered to no address the key covers is a 404.

Also available in

API
GET /threads/{id}/team
TypeScript
nextmail.threads.getTeam()
Python
threads.get_team()
Ruby
threads.get_team
PHP
threads->getTeam
Go
Threads.GetTeam
Java
threads().getTeam
CLI
nextmail threads get-team

Threads.AssignAsync

Assign a thread

Scopesthreads:write
Signature
Task<JsonObject> AssignAsync(    string id,    string assigneeId,    string? apiKey = null,    CancellationToken cancellationToken = default)

Gives the thread to one person, who is emailed unless they assigned it to themselves.

Only people who can see a thread can be given it or mentioned on it: the workspace owner, members who reach every address, and members who hold its address or its domain. A key acts for the workspace owner.

Parameters

idstringRequired

Thread id.

assigneeIdstringRequired

The user id of the person to give it to, as GetTeamAsync lists them in team.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to thread_assignment, threadId, assignee, assignedBy, assignedAt, doneBy and doneAt: who has the thread now.

Example

var assignment = await client.Threads.AssignAsync("CAHk7pQ2x9LmZ4-mail.example.com", "usr_2f9c1d"); Console.WriteLine($"{assignment["assignee"]?["email"]?.ToString() ?? "nobody"}");

Notes

Also available in

API
PUT /threads/{id}/assignee
TypeScript
nextmail.threads.assign()
Python
threads.assign()
Ruby
threads.assign
PHP
threads->assign
Go
Threads.Assign
Java
threads().assign
CLI
nextmail threads assign

Threads.UnassignAsync

Unassign a thread

Scopesthreads:write
Signature
Task<JsonObject> UnassignAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Leaves the thread with nobody.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with the fields AssignAsync returns and assignee null.

Example

var assignment = await client.Threads.UnassignAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine(assignment["assignee"] is null ? "Nobody has it" : "Still assigned");

Also available in

API
DELETE /threads/{id}/assignee
TypeScript
nextmail.threads.unassign()
Python
threads.unassign()
Ruby
threads.unassign
PHP
threads->unassign
Go
Threads.Unassign
Java
threads().unassign
CLI
nextmail threads unassign

Threads.MarkDoneAsync

Mark a thread done

Scopesthreads:write
Signature
Task<JsonObject> MarkDoneAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Archives the thread for everyone and records who marked it done. A new reply brings it back to the inbox, and ReopenAsync moves it back by hand.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with the fields AssignAsync returns, with doneBy and doneAt set.

Example

var assignment = await client.Threads.MarkDoneAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine($"Done at {assignment["doneAt"]}");

Also available in

API
POST /threads/{id}/done
TypeScript
nextmail.threads.markDone()
Python
threads.mark_done()
Ruby
threads.mark_done
PHP
threads->markDone
Go
Threads.MarkDone
Java
threads().markDone
CLI
nextmail threads mark-done

Threads.ReopenAsync

Reopen a thread

Scopesthreads:write
Signature
Task<JsonObject> ReopenAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Moves a done thread back to the inbox for everyone.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with the fields AssignAsync returns, with doneBy and doneAt null.

Example

var assignment = await client.Threads.ReopenAsync("CAHk7pQ2x9LmZ4-mail.example.com"); Console.WriteLine(assignment["doneAt"] is null ? "Back in the inbox" : "Still done");

Also available in

API
POST /threads/{id}/reopen
TypeScript
nextmail.threads.reopen()
Python
threads.reopen()
Ruby
threads.reopen
PHP
threads->reopen
Go
Threads.Reopen
Java
threads().reopen
CLI
nextmail threads reopen

Threads.ListCommentsAsync

List the comments on a thread

Scopesthreads:read
Signature
Task<IReadOnlyList<JsonObject>> ListCommentsAsync(    string id,    string? apiKey = null,    CancellationToken cancellationToken = default)

Returns the team comments on one thread, oldest first. Everyone who can see the thread reads them, and the sender never does.

Parameters

idstringRequired

Thread id.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

An IReadOnlyList<JsonObject>, each with id, threadId, author, body, mentions, createdAt and editedAt.

Example

var comments = await client.Threads.ListCommentsAsync("CAHk7pQ2x9LmZ4-mail.example.com"); foreach (var comment in comments){    Console.WriteLine($"{comment["author"]?["email"]?.ToString() ?? "removed"}: {comment["body"]}");}

Notes

  • A thread delivered to no address the key covers is a 404.

Also available in

API
GET /threads/{id}/comments
TypeScript
nextmail.threads.listComments()
Python
threads.list_comments()
Ruby
threads.list_comments
PHP
threads->listComments
Go
Threads.ListComments
Java
threads().listComments
CLI
nextmail threads list-comments

Threads.CreateCommentAsync

Comment on a thread

Scopesthreads:write
Signature
Task<JsonObject> CreateCommentAsync(    string id,    IReadOnlyDictionary<string, object?> body,    string? apiKey = null,    CancellationToken cancellationToken = default)

Adds a comment for the team. Everybody in mentions is emailed.

Only people who can see a thread can be given it or mentioned on it: the workspace owner, members who reach every address, and members who hold its address or its domain. A key acts for the workspace owner.

Parameters

idstringRequired

Thread id.

bodystringRequired

What the comment says, up to 10,000 characters. Leading and trailing spaces are trimmed.

mentionsstring[]

The user ids of people to mention, at most 20. Each has to be able to see the thread.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject for the new comment, with the fields ListCommentsAsync returns.

Example

var comment = await client.Threads.CreateCommentAsync("CAHk7pQ2x9LmZ4-mail.example.com", new Body{    ["body"] = "Can you take this one?",    ["mentions"] = new[] { "usr_2f9c1d" },}); Console.WriteLine($"{comment["id"]}");

Notes

  • The SDK does not retry it, because a second call would add a second comment.

  • Somebody in mentions who cannot see the thread is a 422 invalid_parameter on mentions.

Also available in

API
POST /threads/{id}/comments
TypeScript
nextmail.threads.createComment()
Python
threads.create_comment()
Ruby
threads.create_comment
PHP
threads->createComment
Go
Threads.CreateComment
Java
threads().createComment
CLI
nextmail threads create-comment

Threads.UpdateCommentAsync

Change a comment

Scopesthreads:write
Signature
Task<JsonObject> UpdateCommentAsync(    string id,    string commentId,    IReadOnlyDictionary<string, object?> body,    string? apiKey = null,    CancellationToken cancellationToken = default)

Replaces the text and the mentions of a comment. Only its author can change it, and anybody newly mentioned is emailed.

Parameters

idstringRequired

Thread id.

commentIdstringRequired

The comment id, as ListCommentsAsync returns it.

bodystringRequired

The new text, up to 10,000 characters.

mentionsstring[]

The user ids it mentions now, at most 20. Leave it out to mention nobody.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject for the comment as it is now, with the fields ListCommentsAsync returns.

Example

var comment = await client.Threads.UpdateCommentAsync("CAHk7pQ2x9LmZ4-mail.example.com", "tcm_8a1f", new Body { ["body"] = "Done, sent the quote." }); Console.WriteLine($"Edited at {comment["editedAt"]}");

Notes

  • Someone else's comment, or one on another thread, is a 404.

Also available in

API
PATCH /threads/{id}/comments/{commentId}
TypeScript
nextmail.threads.updateComment()
Python
threads.update_comment()
Ruby
threads.update_comment
PHP
threads->updateComment
Go
Threads.UpdateComment
Java
threads().updateComment
CLI
nextmail threads update-comment

Threads.DeleteCommentAsync

Delete a comment

Scopesthreads:write
Signature
Task<JsonObject> DeleteCommentAsync(    string id,    string commentId,    string? apiKey = null,    CancellationToken cancellationToken = default)

Deletes a comment for good. Its author can delete it, and so can the workspace owner, which a key acts for.

Parameters

idstringRequired

Thread id.

commentIdstringRequired

The comment id, as ListCommentsAsync returns it.

apiKeystring?

Overrides the client's API key for this call only.

cancellationTokenCancellationToken

Cancels the request.

Returns

A JsonObject with object set to thread_comment, id, threadId and deleted set to true.

Example

var deleted = await client.Threads.DeleteCommentAsync("CAHk7pQ2x9LmZ4-mail.example.com", "tcm_8a1f"); Console.WriteLine($"{deleted["id"]} deleted");

Notes

  • The SDK does not retry a delete. A 404 on your own second attempt after a lost response means the first one worked.

Also available in

API
DELETE /threads/{id}/comments/{commentId}
TypeScript
nextmail.threads.deleteComment()
Python
threads.delete_comment()
Ruby
threads.delete_comment
PHP
threads->deleteComment
Go
Threads.DeleteComment
Java
threads().deleteComment
CLI
nextmail threads delete-comment