Skip to the documentation
API

Emails

Every operation in this group: what it accepts, what it returns and the errors it can answer with.

Operations

Send, schedule, cancel and retrieve.

POST/emails

Send an email

Scopesemails:sendSends mail
Honours Idempotency-Key

Sends now, or schedules with scheduledAt. Answers 200 when the message has already gone and 202 when something still has to happen to it, so a caller may branch on the status code. Honours Idempotency-Key.

The body can come from html, text, a stored template, or an existing draftId: one of the four, never two.

Add translate to send it in the recipient's language rather than yours. It is resolved at accept time, before any record of the message exists, so a scheduled send carries the words that were approved and a translation that could not be produced refuses the send rather than delivering the original. Works with template, which is the useful case: the rendered output is what gets translated. See the Languages section, and POST /emails/translate to show somebody the result first.

Headers

Idempotency-Keystring

Makes a retry safe. Reusing one with a different body is a 422.

Up to 255 charactersPattern^[A-Za-z0-9_.:-]+$

Request body

fromstring | objectRequired

Sender as billing@acme.com, Acme Billing <billing@acme.com> or { email, name }. It must be an address the key may send as, otherwise 403 from_address_forbidden.

emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
to(string | object)[]Required

One recipient or a list. to, cc and bcc together hold at most 50 addresses, and more is a 422 too_many_recipients.

1 to 50 items
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
cc(string | object)[]

Copy recipients, counted toward the 50 recipient ceiling.

Up to 50 itemsDefault[]
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
bcc(string | object)[]

Blind copy recipients, counted toward the 50 recipient ceiling.

Up to 50 itemsDefault[]
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
replyTostring | object

Written into the Reply-To header.

emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
subjectstring

At most 998 characters. Falls back to the template or draft subject when empty.

Up to 998 charactersDefault""
htmlstring

HTML body, at most 1,000,000 characters.

Up to 1000000 characters
textstring

Plain text body, at most 1,000,000 characters.

Up to 1000000 characters
templateobject

A stored template by id or slug. Omitting version resolves whatever is published at that moment, so pin it when somebody else owns the copy.

idstringRequired
1 to 128 characters
versioninteger
More than 0At most 100000
propsRecord<string, any>
slotsRecord<string, any>
translateobject

{ to, from?, includeOriginal?, subject? }. to takes a code, an English name or an endonym. includeOriginal and subject both default to true.

tostringRequired
2 to 60 characters
fromstring
2 to 60 characters
includeOriginalboolean
Defaulttrue
subjectboolean
Defaulttrue
headersRecord<string, string>

Custom headers limited to X-*, List-*, Reply-To, Precedence, Auto-Submitted, Importance, Priority and Feedback-ID. Anything the server sets itself is a 422 reserved_header.

Default{}
attachmentsobject[]

Up to 20 files, in three forms. { filename, content, contentType? } carries the file as base64, up to 10 MB for all of them together. { url, filename?, contentType? } names a file at an https address, which is fetched once when the message is accepted, within 15 seconds and the 25 MB the message has room for; the name and type default to the last part of the path and the type the server answered with, and an address that cannot be fetched is a 422 attachment_unreachable. { fileId } names a file uploaded with POST /files, up to 100 MB. A file given as base64 or by url is kept with the message until it goes out, so it is never fetched twice and a retry of the send carries the same bytes.

Up to 20 itemsDefault[]
filenamestringRequired
1 to 255 characters
contentstringRequired
1 to 13981022 charactersPattern^[A-Za-z0-9+/=\r\n]+$
contentTypestring
Up to 255 charactersPattern^[\w.+-]+\/[\w.+-]+(?:[ \t]*;[ \t]*[\w.+-]+=(?:"[^"\r\n]*"|[\w.+-]+))*$
attachmentDeliverystring

How the files in attachments travel. mime carries them inside the message, the way mail always has, so files that come to more than 25 MB together are refused. link uploads each file and puts a download link in the body in its place, so the message itself stays small. auto links when the files together come to more than 25 MB, or to more than 2 MB when the from domain has an active files domain, and carries them inside the message otherwise. Left out, the sender's mailbox setting applies, and that defaults to auto. A download link uses the files domain when the from domain has one active and the default Nextmail host otherwise.

One of"mime""link""auto"
threadIdstring

Files the sent message into an existing thread.

Up to 256 characters
draftIdstring

Sends an existing draft as written. Cannot be combined with template or translate.

Up to 256 characters
scheduledAtstring

A Date, an ISO 8601 instant or a duration such as PT1H or P2D. At least one second and at most 365 days out.

3 to 64 characters
followUpAtstring

Asks to be reminded if nobody replies: an ISO 8601 instant, or a duration from now such as P3D, later than the time the message goes out and at most 365 days out. If no real reply has arrived by then, the thread returns to the top of the inbox, unread, with a push. GET /follow-ups lists the reminders and DELETE /follow-ups/{id} cancels one.

3 to 64 characters
cancellableForSecondsinteger

An undo window from 0 to 900 seconds on an immediate send. Refused alongside scheduledAt, which is already cancellable until it goes.

At least 0At most 900Default0
trackingobject

Open and link tracking for this send alone. A field left out takes the from address's own setting, then its domain's catch-all's when the catch-all caught that address rather than it being one you created, and is off when neither sets it. Set them per address with PATCH /settings?address=.

opensboolean
clicksboolean
signatureboolean

An html body goes out exactly as written, so it carries a signature only when this is true, while a text-only body carries one unless this is false. When it is added it is the default signature of the from address: the one it keeps for replies when the message answers a thread (threadId or an In-Reply-To header), and the one for new mail otherwise. An address with no signature of its own takes its domain catch-all's when the catch-all caught that address, else the Nextmail footer unless that address turned the footer off. signatureId picks another signature of the address. Template sends and encrypted sends never carry one.

signatureIdstring

Adds one particular signature of the from address in place of its default, by the id GET /signatures returns. It signs an html body too, so signature: true is not needed with it, and it cannot be combined with signature: false. An id that is not a signature of the from address is a 422 invalid_parameter. When the signature is deleted before a scheduled message goes out, the message carries the default of the address instead.

1 to 64 characters
streamstring

The lane the message travels in. transactional, the default, is for mail one person is waiting for, such as a password reset, a receipt or a code. broadcast is for newsletters and other mail to many people at once. Each lane has its own queue and its own reputation, so a newsletter never holds up or harms app mail, and the broadcast lane pauses itself when too many of its messages bounce or are reported as spam. A message sent to a paused lane is a 409 stream_paused.

On the broadcast lane, a message to one recipient that sets no List-Unsubscribe header of its own carries List-Unsubscribe and List-Unsubscribe-Post for one-click unsubscribe. Whoever uses it joins the unsubscribe list (GET /suppressions/unsubscribes), and broadcast lane mail is not sent to anybody on that list: the recipient is marked suppressed and email.suppressed says why. Transactional mail still reaches them.

One of"transactional""broadcast"
tagsRecord<string, string>

Up to 10 tags, keys of 1 to 64 letters, digits, _ or -, values up to 256 characters. Echoed back on every read.

Default{}

Returns

Sent.

Accepted: queued or scheduled.

Errors

409

domain_not_sendable: the from domain is known to this workspace but its signing records are not in DNS yet, so nothing was accepted. Or translation_not_configured: this workspace has no AI configured, so translate cannot be honoured.

429

send_quota_exceeded: this workspace has spent the monthly send allowance of its plan, which counts sends from this workspace alone. It resets on the first of the month. Or ai_quota_exceeded: with translate, this workspace has used today's AI actions. Nothing was sent, and it resets at midnight UTC.

rate_limited: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in Retry-After.

503

The translator did not answer. Nothing was sent; the request is worth retrying.

The errors every operation can return400401403404422500Error catalog

Also available in

TypeScript
nextmail.emails.send()
Python
emails.send()
Ruby
emails.send
PHP
emails->send
Go
Emails.Send
Java
emails().send
C#
Emails.SendAsync
CLI
nextmail emails send
MCP
replyToEmailsendEmail

GET/emails

List sent messages

Scopesemails:readReads

Newest first, a page at a time. A key limited to particular addresses or domains lists only the messages sent from addresses it covers, and that filter runs before the page is cut, so every page but the last is full.

Requires the emails:read scope.

Query parameters

statusstring

One or more of the values an email's status takes, comma-separated. bounced,partial lists every message that did not reach everybody it went to.

fromstring

A bare sending address such as billing@acme.com, matched exactly and case insensitively. A display name form does not match.

broadcastIdstring

Only the copies of one broadcast, a brd_ id from POST /broadcasts. Every person a broadcast reaches gets a message of their own, so this is the list of who it went to and what happened to each copy. An id that names no broadcast answers an empty page.

1 to 64 characters
scheduledFromstring

Only messages scheduled for this instant or later, ISO 8601 with a zone. With scheduledTo and status=scheduled,queued it lists what is waiting to go out in a window, as the calendar of the app does. A message with no scheduledAt is left out.

Formatdate-time
scheduledTostring

Only messages scheduled for this instant or earlier. scheduledFrom after scheduledTo is a 422 invalid_parameter.

Formatdate-time
sincestring

Only messages made at this instant or later, ISO 8601 with a zone.

Formatdate-time
untilstring

Only messages made before this instant. since after until is a 422 invalid_parameter.

Formatdate-time
recipientstring

Only messages that went to this address, as a to, cc or bcc recipient. The whole address, matched exactly.

Up to 320 characters
tagstring

Only messages sent with this tag: name for any value, or name:value for one value, as tags on POST /emails set them. Tags are stored encrypted, so the match is exact and case-sensitive.

Up to 330 characters
limitinteger

Rows per page, a whole number from 1 to 100, defaulting to 25. Outside that range is a 422.

At least 1At most 100Default25
cursorstring

A message id. Keyset, not offset.

Returns

A page of messages, newest first.

Errors

The errors every operation can return400401403404422429500Error catalog

Also available in

TypeScript
nextmail.emails.list()nextmail.emails.listAll()nextmail.emails.iterate()
Python
emails.list()emails.list_all()emails.iterate()
Ruby
emails.listemails.list_allemails.iterate
PHP
emails->listemails->listAllemails->iterate
Go
Emails.ListEmails.ListAllEmails.Iterate
Java
emails().listemails().listAllemails().iterate
C#
Emails.ListAsyncEmails.ListAllAsyncEmails.IterateAsync
CLI
nextmail emails list
MCP
listSentEmails

POST/emails/batch

Send up to 100 messages

Scopesemails:sendSends mail
Honours Idempotency-Key

Per item, never all-or-nothing: a batch that rolled back on one bad address would make the caller's retry a question of which messages had already gone. Honours Idempotency-Key.

Headers

Idempotency-Keystring

Makes a retry of the whole batch safe. Each message is keyed as the key, a slash and its index, so a retry with the same key replays the messages that already went, with replayed: true, and sends only the rest. Reusing it with a different message at an index refuses that item with idempotency_key_reuse.

Up to 255 charactersPattern^[A-Za-z0-9_.:-]+$

Request body

emailsSendEmailRequest[]
Up to 100 items

Returns

Per-item results, in the order the messages were sent.

Errors

429

This workspace has spent the monthly send allowance of its plan, which counts sends from this workspace alone. It resets on the first of the month.

rate_limited: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in Retry-After.

The errors every operation can return400401403404422500Error catalog

Also available in

TypeScript
nextmail.emails.sendBatch()
Python
emails.send_batch()
Ruby
emails.send_batch
PHP
emails->sendBatch
Go
Emails.SendBatch
Java
emails().sendBatch
C#
Emails.SendBatchAsync
CLI
nextmail emails send-batch

POST/emails/translate

Translate a message without sending it

Scopesemails:sendReads

The same round trip translate makes on a send, stopped one step early. The same function produces both, so what this shows is what would go out.

No scope of its own, deliberately. It grants nothing a sender could not already do, and a scope nobody can tell apart from emails:send on a consent screen makes every other scope on that list mean slightly less.

The body is capped at the same megabyte the send path allows, but translation itself refuses anything over 30,000 characters with translation_too_long. That is a refusal and not a truncation on purpose: half a translated message has no seam to show where it stopped, and the person reading acts on the half they were given.

Requires the emails:send scope.

Request body

htmlstring

HTML body to translate. Only the content inside <body> is sent to the model when the markup is a full document.

Up to 1000000 characters
textstring

Plain text body to translate. Translated separately when given alongside html.

Up to 1000000 characters
subjectstring

Subject line to translate, at most 998 characters.

Up to 998 characters
tostringRequired

Target language as a code (de), English name (German) or endonym (Deutsch). An unrecognised value is a 422 invalid_parameter on to.

2 to 60 characters
fromstring

The language you wrote in. Stating it skips the detection call.

2 to 60 characters
includeOriginalboolean

Defaults to true, placing your original text below the translation under a caption in the target language.

Defaulttrue

Returns

The translation. Nothing was sent.

Errors

409

This workspace has no AI configured, so nothing can be translated.

429

ai_quota_exceeded: this workspace has used today's AI actions. It resets at midnight UTC.

rate_limited: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in Retry-After.

503

The translator did not answer. Nothing was sent; the request is worth retrying.

The errors every operation can return400401403404422500Error catalog

Also available in

TypeScript
nextmail.emails.translate()
Python
emails.translate()
Ruby
emails.translate
PHP
emails->translate
Go
Emails.Translate
Java
emails().translate
C#
Emails.TranslateAsync
CLI
nextmail emails translate
MCP
previewTranslation

POST/emails/check

Check how a message would be rated, without sending it

Scopesemails:sendReads

Scores a message the way a receiving mailbox would, before you send it: a spam score, a phishing score and an AI-writing score, each 0 to 100, with the signals behind them. Run it while someone writes, or before an automated send, and fix what it names.

The same checks score every message that arrives in a mailbox on Nextmail, so what you see here is what a recipient on Nextmail sees in Details. It cannot know a recipient's own filter, sender history or reputation, so a low score is a good sign and not a delivery guarantee.

No scope of its own, for the same reason as the translation preview: it grants nothing a sender could not already do. It spends no AI action and never calls a model.

Requires the emails:send scope.

Request body

fromstring

The address it will be sent from.

Up to 320 characters
fromNamestring

The display name it will carry. A name that claims another address or a known brand raises the phishing score.

Up to 320 characters
replyTostring

A Reply-To on a different domain raises the phishing score.

Up to 320 characters
subjectstring

Subject line, at most 998 characters.

Up to 998 charactersDefault""
htmlstring

HTML body. Links and images are read from it.

Up to 200000 characters
textstring

Plain text body. Taken from html when left out.

Up to 200000 characters
replyingboolean

True when it answers an existing thread. A Re: subject on a message that answers nothing raises the spam score.

attachmentNamesstring[]

File names, so an attachment that can run code is caught.

Up to 100 items

Returns

The scores. Nothing was stored or sent.

Errors

The errors every operation can return400401403404422429500Error catalog

Also available in

TypeScript
nextmail.emails.check()
Python
emails.check()
Ruby
emails.check
PHP
emails->check
Go
Emails.Check
Java
emails().check
C#
Emails.CheckAsync
CLI
nextmail emails check
MCP
checkEmail

PATCH/emails/{id}

Change a message that has not gone yet

Scopesemails:sendChanges data

Moves it with scheduledAt, and changes what it says or who it goes to with subject, html, text, from, to, cc and bcc, while it is still queued or scheduled. Send any of them together. A recipient list replaces the stored one whole, and from is checked as it is on a send, so it has to be an address the key may send as. A message that was translated when it was accepted keeps its wording, and one that was encrypted keeps its wording and its recipients: cancel it and send again instead.

Path parameters

idstringRequired

The msg_ send id to move.

Request body

scheduledAtstring

When it goes out instead: an ISO 8601 instant, or a duration such as PT2H, up to a year out.

3 to 64 characters
subjectstring

The new subject, up to 998 characters.

Up to 998 characters
htmlstring

The new HTML body.

Up to 1000000 characters
textstring

The new plain text body.

Up to 1000000 characters
fromstring

The address it goes out as, checked as it is on a send.

3 to 320 characters
to(string | object)[]

Replaces the stored recipients whole. So do cc and bcc.

1 to 50 items
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
cc(string | object)[]

Replaces the copied recipients.

Up to 50 items
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
bcc(string | object)[]

Replaces the blind copied recipients.

Up to 50 items
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters

Returns

The message as it is now.

Errors

409

email_not_cancellable: it has already gone or was cancelled. translation_locked: it was translated when it was accepted, so its wording cannot change.

The errors every operation can return400401403404422429500Error catalog

Also available in

TypeScript
nextmail.emails.reschedule()nextmail.emails.update()
Python
emails.reschedule()emails.update()
Ruby
emails.rescheduleemails.update
PHP
emails->rescheduleemails->update
Go
Emails.RescheduleEmails.Update
Java
emails().rescheduleemails().update
C#
Emails.RescheduleAsyncEmails.UpdateAsync
CLI
nextmail emails reschedulenextmail emails update
MCP
rescheduleEmailupdateScheduledEmail

POST/emails/{id}/cancel

Cancel a message

Scopesemails:sendDeletes

Idempotent: cancelling twice returns the same cancelled message. A 409 means it has already gone and cannot be un-sent.

Path parameters

idstringRequired

The msg_ send id to cancel.

Returns

Cancelled.

Errors

409

No longer cancellable.

The errors every operation can return400401403404422429500Error catalog

Also available in

TypeScript
nextmail.emails.cancel()
Python
emails.cancel()
Ruby
emails.cancel
PHP
emails->cancel
Go
Emails.Cancel
Java
emails().cancel
C#
Emails.CancelAsync
CLI
nextmail emails cancel
MCP
cancelEmail

GET/emails/{id}/events

What happened to a message

Scopesemails:readReads

The event trail, oldest first, one page at a time. Nothing is dropped from it: a tracked message records an event for every counted open, click and download, so a widely read message runs to many pages, and following nextCursor while hasMore is true reaches the newest event.

Requires the emails:read scope.

Path parameters

idstringRequired

The msg_ send id whose trail to read.

Query parameters

limitinteger

Rows per page, 1 to 100.

At least 1At most 100Default25
cursorstring

An event id. Keyset, not offset: pass the previous page's nextCursor. One that names nothing in this list is a 400 invalid_cursor.

Returns

A page of the event trail, oldest first.

Errors

The errors every operation can return400401403404422429500Error catalog

Also available in

TypeScript
nextmail.emails.listEvents()nextmail.emails.listAllEvents()nextmail.emails.iterateEvents()
Python
emails.list_events()emails.list_all_events()emails.iterate_events()
Ruby
emails.list_eventsemails.list_all_eventsemails.iterate_events
PHP
emails->listEventsemails->listAllEventsemails->iterateEvents
Go
Emails.ListEventsEmails.ListAllEventsEmails.IterateEvents
Java
emails().listEventsemails().listAllEventsemails().iterateEvents
C#
Emails.ListEventsAsyncEmails.ListAllEventsAsyncEmails.IterateEventsAsync
CLI
nextmail emails list-events
MCP
listEmailEvents

POST/emails/compose

Write an email with AI

Scopesemails:sendReads

Writes the body of an email from prompt, an instruction or a few rough notes, in the style of the mail this workspace has sent before, as the composer of the app does. Give threadId to write a reply: the messages of that thread are read as context, which also needs threads:read. Give draft, the body written so far, to have prompt change it instead, and withSubject to get a subject as well. Give send, the send options as they stand, to let prompt change them too, as the describe bar of the composer does: the recipients, when it sends, tracking, the signature, the language, the address it goes out as (give from and send.fromOptions) and end-to-end encryption (give send.encrypt). Give templates: true to let prompt switch it to one of the workspace's published templates, which needs templates:read, and files: true to let it attach files the workspace holds, which needs files:read. The answer then carries send with only what changed, in the shape POST /emails takes, and times are read in timeZone or the time zone of the account. Nothing is saved or sent: pass the result to POST /emails or POST /drafts. It spends one AI action.

Requires the emails:send scope.

Request body

promptstringRequired

What to write: an instruction, a rough draft or a few notes. With draft, what to change in it, and it may be empty to have the draft finished as it is.

Up to 20000 characters
subjectstring

The subject so far, if there is one.

Up to 998 characters
fromstring

The address it goes out as, so it is written as that address.

1 to 320 characters
templatestring

The id of the template the email uses now. Its body is then the template, so prompt leaves the body alone.

1 to 128 characters
templatesboolean

Let prompt switch the email to one of the workspace's published templates, which needs templates:read. The answer names it in template.

filesboolean

Let prompt attach files the workspace holds, which needs files:read. The answer lists them in attach, each ready to send as { "fileId": id }.

withSubjectboolean

Also write a subject. The answer then carries subject: a new one when there was none or prompt asks for one, otherwise the one given.

draftstring

The body written so far, as text or Markdown. prompt then says what to change, and body comes back as the whole draft with that change made.

Up to 20000 characters
tostring[]

Who it goes to, so the greeting and tone fit.

Up to 50 items
ccstring[]

Who is copied.

Up to 50 items
bccstring[]

Who is blind copied.

Up to 50 items
sendobject

The send options as they stand. Give it, even empty, to let prompt change them: the answer then carries send with what changed.

scheduledAtstring

When it is set to send, as an ISO-8601 time, or null when it sends right away.

Can be nullUp to 64 characters
trackingobject

Whether opens and clicks are tracked as it stands.

opensboolean
clicksboolean
signatureboolean

Whether the signature is added as it stands.

translateobject

The language it is translated into as it stands, or null when it is not.

Can be null
tostringRequired

A language code such as de, or a language name.

2 to 60 characters
fromOptionsstring[]

The addresses it may go out as. prompt can then move it to one of them, and the answer names it in send.from.

Up to 100 items
encryptboolean

Whether it is encrypted end to end as it stands. Give it only when every recipient can receive encrypted mail, so prompt can turn encryption on or off.

timeZonestring

The IANA time zone to read times in, such as Europe/Berlin. Without it, the time zone in the account settings.

1 to 64 characters
threadIdstring

A thread to reply in. Its messages are read as context, and it needs threads:read.

1 to 200 characters

Returns

The body that was written.

Errors

409

ai_not_configured: AI writing is not available on this server.

429

ai_quota_exceeded: this workspace has used the AI actions of its plan for today. Nothing was written, and it resets at midnight UTC.

rate_limited: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in Retry-After.

The errors every operation can return400401403404422500Error catalog

Also available in

TypeScript
nextmail.emails.compose()
Python
emails.compose()
Ruby
emails.compose
PHP
emails->compose
Go
Emails.Compose
Java
emails().compose
C#
Emails.ComposeAsync
CLI
nextmail emails compose
MCP
composeEmail

POST/emails/rewrite

Rewrite part of an email with AI

Scopesemails:sendReads

Rewrites a subject or a body and answers with up to five different versions, as the rewrite menu of the composer does. action is shorten, lengthen, rephrase, formal, casual or custom, which needs instruction to say what to change. Give threadId when the text is a reply, so the rewrite fits the conversation: its messages are read as context, which also needs threads:read. Nothing is saved. It spends one AI action.

Requires the emails:send scope.

Request body

targetstring

body or subject. Defaults to body.

One of"subject""body"Default"body"
textstringRequired

The subject or body to rewrite.

1 to 100000 characters
actionstringRequired

What to do: shorten, lengthen, rephrase, formal, casual or custom.

One of"shorten""lengthen""rephrase""formal""casual""custom"
instructionstring

What to change, up to 500 characters. Required with custom.

Up to 500 characters
countinteger

How many versions, 1 to 5. Defaults to 3.

At least 1At most 5Default3
threadIdstring

The thread the text replies in, read as context.

1 to 200 characters

Returns

The versions, best first.

Errors

409

ai_not_configured: AI writing is not available on this server.

429

ai_quota_exceeded: this workspace has used the AI actions of its plan for today. Nothing was written, and it resets at midnight UTC.

rate_limited: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in Retry-After.

The errors every operation can return400401403404422500Error catalog

Also available in

TypeScript
nextmail.emails.rewrite()
Python
emails.rewrite()
Ruby
emails.rewrite
PHP
emails->rewrite
Go
Emails.Rewrite
Java
emails().rewrite
C#
Emails.RewriteAsync
CLI
nextmail emails rewrite
MCP
rewriteEmail

POST/emails/subject

Suggest a subject line

Scopesemails:sendReads

Reads the body of an email and suggests a short subject for it, under 100 characters, in the style of the mail this workspace has sent. Nothing is saved. It spends one AI action.

Requires the emails:send scope.

Request body

messagestringRequired

The body of the email, as text or HTML.

1 to 100000 characters

Returns

The suggested subject.

Errors

409

ai_not_configured: AI writing is not available on this server.

429

ai_quota_exceeded: this workspace has used the AI actions of its plan for today. Nothing was written, and it resets at midnight UTC.

rate_limited: the key or app made more than 600 requests in 60 seconds. Wait for the seconds in Retry-After.

The errors every operation can return400401403404422500Error catalog

Also available in

TypeScript
nextmail.emails.suggestSubject()
Python
emails.suggest_subject()
Ruby
emails.suggest_subject
PHP
emails->suggestSubject
Go
Emails.SuggestSubject
Java
emails().suggestSubject
C#
Emails.SuggestSubjectAsync
CLI
nextmail emails suggest-subject
MCP
suggestSubject

Objects

BatchSendItemobject

indexintegerRequired

Where the message sat in emails.

At least 0
statusstringRequired
One of"ok""error"
emailobject

The message, when status is ok.

objectstring
One of"email"
idstring

The durable handle, msg_ + 24 hex.

statusstring

Where the message is in its life. sent means it left: the sending service took it, which is not yet delivery, and a bounce reported afterwards still moves it on. bounced means every recipient it went to bounced, so nobody has it, and each entry in recipients carries the reason. partial means some recipients have it and some do not, whether the transport stopped part way or some of them bounced later; retrying is wrong, because the ones who have it cannot be un-sent.

One of"queued""scheduled""sending""sent""partial""bounced""cancelled""failed"
modestring
One of"live""test"
fromstring
subjectstring
Can be null
messageIdstring

RFC 5322 Message-ID. Null until the MIME exists. Do NOT correlate on it: the header is rewritten on the way out, so the value here appears in no bounce or delivery report and a match on it never fires. A delivery event names the send by its id, as emailId.

Can be null
threadIdstring
Can be null
transportstring

How the bytes left, once they have. Null until dispatch. test is what a message sent with an oe_test_ key records: it was accepted and every recipient marked delivered, but nothing was carried. dev is not a way of sending either: it is what a message records where nothing is configured to carry mail, having been built and sent nowhere. connected means it left through the outgoing server of a connected mailbox, such as Gmail, and not through Nextmail.

Can be nullOne of"ses""test""dev""connected"
attemptsinteger
lastErrorstring
Can be null
scheduledAtstring
Can be nullFormatdate-time
cancellableUntilstring
Can be nullFormatdate-time
sentAtstring
Can be nullFormatdate-time
tagsRecord<string, string>
broadcastIdstring

The brd_ broadcast this message is one copy of, or null for a message sent on its own. GET /emails?broadcastId= lists every copy of one broadcast.

Can be null
sourcestring
One of"api""oauth""composer""mcp""ai""form""mail-app""booking"
streamstring

The lane the message travels in: transactional for app mail, broadcast for newsletters and every copy of a broadcast.

One of"transactional""broadcast"
createdAtstring
Formatdate-time
recipientsobject[]

Returned on retrieval only.

emailstring
namestring
Can be null
kindstring
One of"to""cc""bcc"
statusstring

uncertain is real and is shown as itself: a transport that failed part-way cannot say which recipients it reached, and calling those delivered or failed would both be guesses. suppressed is decided before dispatch rather than reported afterwards: the address bounced or complained in this workspace before, so this copy was never offered to the transport. A message whose recipients are all suppressed fails outright.

One of"pending""delivered""failed""bounced""complained""suppressed""uncertain"
errorstring
Can be null
deliveredAtstring
Can be nullFormatdate-time
translationobject

Present only when the message was translated, and only on responses that carry the stored request, which are the send itself and a retrieval. A list row does not fetch it, so its absence there says nothing either way.

languagestring

The resolved target code: de, pt-BR.

languageNamestring

Its English name.

detectedSourceLanguagestring

Stated or detected. Null when detection abstained.

Can be null
subjectboolean

Whether the subject was translated too.

includeOriginalboolean

Whether the sender's own words went below the translation.

trackingTracking
replayedboolean

True when an Idempotency-Key replayed an earlier send of this item.

errorobject

Why the message was refused, when status is error: the fields of a whole-request error, without requestId.

typestringRequired
One of"invalid_request_error""authentication_error""permission_error""not_found_error""conflict_error""validation_error""rate_limit_error""api_error"
codestringRequired
messagestringRequired
paramstring

The offending field, dotted.

BatchSendResultobject

objectstringRequired
One of"list"
dataBatchSendItem[]Required

One entry per message, in the order they were sent.

sentintegerRequired

How many messages were accepted.

At least 0
failedintegerRequired

How many were refused. Their entries say why.

At least 0

Emailobject

objectstring
One of"email"
idstring

The durable handle, msg_ + 24 hex.

statusstring

Where the message is in its life. sent means it left: the sending service took it, which is not yet delivery, and a bounce reported afterwards still moves it on. bounced means every recipient it went to bounced, so nobody has it, and each entry in recipients carries the reason. partial means some recipients have it and some do not, whether the transport stopped part way or some of them bounced later; retrying is wrong, because the ones who have it cannot be un-sent.

One of"queued""scheduled""sending""sent""partial""bounced""cancelled""failed"
modestring
One of"live""test"
fromstring
subjectstring
Can be null
messageIdstring

RFC 5322 Message-ID. Null until the MIME exists. Do NOT correlate on it: the header is rewritten on the way out, so the value here appears in no bounce or delivery report and a match on it never fires. A delivery event names the send by its id, as emailId.

Can be null
threadIdstring
Can be null
transportstring

How the bytes left, once they have. Null until dispatch. test is what a message sent with an oe_test_ key records: it was accepted and every recipient marked delivered, but nothing was carried. dev is not a way of sending either: it is what a message records where nothing is configured to carry mail, having been built and sent nowhere. connected means it left through the outgoing server of a connected mailbox, such as Gmail, and not through Nextmail.

Can be nullOne of"ses""test""dev""connected"
attemptsinteger
lastErrorstring
Can be null
scheduledAtstring
Can be nullFormatdate-time
cancellableUntilstring
Can be nullFormatdate-time
sentAtstring
Can be nullFormatdate-time
tagsRecord<string, string>
broadcastIdstring

The brd_ broadcast this message is one copy of, or null for a message sent on its own. GET /emails?broadcastId= lists every copy of one broadcast.

Can be null
sourcestring
One of"api""oauth""composer""mcp""ai""form""mail-app""booking"
streamstring

The lane the message travels in: transactional for app mail, broadcast for newsletters and every copy of a broadcast.

One of"transactional""broadcast"
createdAtstring
Formatdate-time
recipientsobject[]

Returned on retrieval only.

emailstring
namestring
Can be null
kindstring
One of"to""cc""bcc"
statusstring

uncertain is real and is shown as itself: a transport that failed part-way cannot say which recipients it reached, and calling those delivered or failed would both be guesses. suppressed is decided before dispatch rather than reported afterwards: the address bounced or complained in this workspace before, so this copy was never offered to the transport. A message whose recipients are all suppressed fails outright.

One of"pending""delivered""failed""bounced""complained""suppressed""uncertain"
errorstring
Can be null
deliveredAtstring
Can be nullFormatdate-time
translationobject

Present only when the message was translated, and only on responses that carry the stored request, which are the send itself and a retrieval. A list row does not fetch it, so its absence there says nothing either way.

languagestring

The resolved target code: de, pt-BR.

languageNamestring

Its English name.

detectedSourceLanguagestring

Stated or detected. Null when detection abstained.

Can be null
subjectboolean

Whether the subject was translated too.

includeOriginalboolean

Whether the sender's own words went below the translation.

trackingTracking

EmailCheckobject

How a receiving mailbox would likely rate this message, scored from its own content before it is sent. Every score runs 0 to 100, and higher means more of the thing it names. Nothing is stored and nothing is sent.

objectstringRequired
One of"email_check"
spamobjectRequired

Spam-like traits in the subject, wording and links: capitals, stacked exclamation marks, stock spam phrases, money or prize bait, link shorteners, an image with almost no text, and a Re: subject on a message that answers nothing.

scoreintegerRequired
At least 0At most 100
levelstringRequired

medium from 35, high from 60.

One of"low""medium""high"
signalsstring[]Required

What raised the score, heaviest first, as stable ids such as spam-phrases or link-shortener.

phishingobjectRequired

What a phishing filter would object to: link text that names a different site, links to a bare IP address, pressure to verify or pay, an attachment that can run code, and a display name that claims another address or a known brand. Sender authentication is taken as passing, since the message will be signed for your domain.

scoreintegerRequired
At least 0At most 100
levelstringRequired

caution from 30, danger from 60.

One of"clear""caution""danger"
signalsstring[]Required
reasonsstring[]Required

One plain sentence per signal, heaviest first.

aiobjectRequired

How much the wording reads as written by a language model, from habits such as stock phrasing, even sentence lengths and markdown. Quoted history and the signature are cut first. It is a score, not a probability, and nobody can prove who wrote a sentence.

scoreintegerRequired

Null when the message was not judged; skipped says why.

Can be nullAt least 0At most 100
levelstringRequired
One of"unknown""unremarkable""possible""likely"
signalsstring[]Required
reasonsstring[]Required
wordsintegerRequired

Words of your own prose that were read.

skippedstringRequired

too-short under 40 words. Null when it was scored.

Can be nullOne of"too-short""encrypted""bulk"

EmailCompositionobject

objectstringRequired
One of"composition"
bodystringRequired

The body that was written, or with draft the whole draft after the change. Text, with Markdown only for links, bold and lists.

subjectstring

The subject, when you asked with withSubject.

sendobject

When you gave send: only the send options prompt changed, empty when it changed none. null takes an option away.

tostring[]

The whole To list after the change.

ccstring[]

The whole Cc list after the change.

bccstring[]

The whole Bcc list after the change.

scheduledAtstring

When to send it, or null to send it right away.

Can be nullFormatdate-time
trackingobject

Whether to track opens and link clicks.

opensboolean
clicksboolean
signatureboolean

Whether to add the signature.

translateobject

The language to send it in, or null to send it untranslated.

Can be null
tostringRequired

A language code, such as de.

fromstring

The address to send it from instead, one of send.fromOptions.

encryptboolean

Whether to encrypt it end to end, when you gave send.encrypt.

templateobject

The template to send it with instead, when you gave templates: true and prompt asked for one. Its body replaces body.

idstringRequired
namestringRequired
attachobject[]

Files the workspace holds to attach, when you gave files: true and prompt asked for them. Send each as { "fileId": id } in attachments.

idstringRequired

The file, as GET /files/{id} reads it.

filenamestringRequired
contentTypestringRequired
sizeintegerRequired

In bytes.

attachMissingstring[]

What prompt asked to attach that matched no file the workspace holds, as prompt named it.

notestring

A short sentence when part of prompt cannot be done this way, such as adding somebody whose address it was not given.

sourcesobject[]

The knowledge base items the draft drew on, when it used any: pinned notes at the levels of the sending address, and the passages that matched the request.

idstring

The knowledge item, as GET /knowledge/{id} reads it.

titlestring
kindstring
One of"note""file""link"
scopestring

The level it sits at: empty for the whole workspace, @ and a domain, or an address.

urlstring

The page a link item reads, or null.

Can be null

EmailListobject

objectstring
One of"list"
hasMoreboolean
nextCursorstring
Can be null

EmailRewriteobject

objectstringRequired
One of"rewrite"
targetstringRequired
One of"subject""body"
variationsstring[]Required

Different versions of the text, never the original itself.

Eventobject

objectstring
One of"event"
idstring
typestring

Dotted, such as email.accepted, email.sent, email.delivered, email.bounced or email.opened.

dataobject

Whatever the event recorded. An empty object when it carries nothing.

createdAtstring
Formatdate-time

EventListobject

objectstring
One of"list"
hasMoreboolean

True when another page follows. Pass nextCursor back as cursor to read it.

nextCursorstring

The id of the last row on this page, or null on the last page.

Can be null

Languageobject

codestringRequired

BCP-47. What every endpoint here accepts and returns.

labelstringRequired

The English name: "Brazilian Portuguese".

nativestringRequired

The endonym, in its own script. Show this first.

flagstringRequired

Two regional-indicator codepoints. A scanning aid beside the native name, never an identifier. Never show it on its own.

rtlbooleanRequired

Right-to-left. A translated body for one of these is wrapped in dir="rtl" before it is sent, because a client that inherits direction renders it backwards otherwise.

SendEmailRequestobject

fromstring | objectRequired
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
to(string | object)[]Required
1 to 50 items
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
cc(string | object)[]
Up to 50 itemsDefault[]
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
bcc(string | object)[]
Up to 50 itemsDefault[]
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
replyTostring | object
emailstringRequired
Up to 320 characters
namestring
Up to 128 characters
subjectstring
Up to 998 charactersDefault""
htmlstring
Up to 1000000 characters
textstring
Up to 1000000 characters
templateobject
idstringRequired
1 to 128 characters
versioninteger
More than 0At most 100000
propsRecord<string, any>
slotsRecord<string, any>
translateobject
tostringRequired
2 to 60 characters
fromstring
2 to 60 characters
includeOriginalboolean
Defaulttrue
subjectboolean
Defaulttrue
headersRecord<string, string>
Default{}
attachmentsobject[]

Up to 20 files, in three forms. { filename, content, contentType? } carries the file as base64, up to 10 MB for all of them together. { url, filename?, contentType? } names a file at an https address, which is fetched once when the message is accepted, within 15 seconds and the 25 MB the message has room for; the name and type default to the last part of the path and the type the server answered with, and an address that cannot be fetched is a 422 attachment_unreachable. { fileId } names a file uploaded with POST /files, up to 100 MB. A file given as base64 or by url is kept with the message until it goes out, so it is never fetched twice and a retry of the send carries the same bytes.

Up to 20 itemsDefault[]
filenamestringRequired
1 to 255 characters
contentstringRequired
1 to 13981022 charactersPattern^[A-Za-z0-9+/=\r\n]+$
contentTypestring
Up to 255 charactersPattern^[\w.+-]+\/[\w.+-]+(?:[ \t]*;[ \t]*[\w.+-]+=(?:"[^"\r\n]*"|[\w.+-]+))*$
attachmentDeliverystring

How the files in attachments travel. mime carries them inside the message, the way mail always has, so files that come to more than 25 MB together are refused. link uploads each file and puts a download link in the body in its place, so the message itself stays small. auto links when the files together come to more than 25 MB, or to more than 2 MB when the from domain has an active files domain, and carries them inside the message otherwise. Left out, the sender's mailbox setting applies, and that defaults to auto. A download link uses the files domain when the from domain has one active and the default Nextmail host otherwise.

One of"mime""link""auto"
threadIdstring
Up to 256 characters
draftIdstring
Up to 256 characters
scheduledAtstring
3 to 64 characters
followUpAtstring

Asks to be reminded if nobody replies: an ISO 8601 instant, or a duration from now such as P3D, later than the time the message goes out and at most 365 days out. If no real reply has arrived by then, the thread returns to the top of the inbox, unread, with a push. GET /follow-ups lists the reminders and DELETE /follow-ups/{id} cancels one.

3 to 64 characters
cancellableForSecondsinteger
At least 0At most 900Default0
trackingobject

Open and link tracking for this send alone. A field left out takes the from address's own setting, then its domain's catch-all's when the catch-all caught that address rather than it being one you created, and is off when neither sets it. Set them per address with PATCH /settings?address=.

opensboolean
clicksboolean
signatureboolean

An html body goes out exactly as written, so it carries a signature only when this is true, while a text-only body carries one unless this is false. When it is added it is the default signature of the from address: the one it keeps for replies when the message answers a thread (threadId or an In-Reply-To header), and the one for new mail otherwise. An address with no signature of its own takes its domain catch-all's when the catch-all caught that address, else the Nextmail footer unless that address turned the footer off. signatureId picks another signature of the address. Template sends and encrypted sends never carry one.

signatureIdstring

Adds one particular signature of the from address in place of its default, by the id GET /signatures returns. It signs an html body too, so signature: true is not needed with it, and it cannot be combined with signature: false. An id that is not a signature of the from address is a 422 invalid_parameter. When the signature is deleted before a scheduled message goes out, the message carries the default of the address instead.

1 to 64 characters
streamstring

The lane the message travels in. transactional, the default, is for mail one person is waiting for, such as a password reset, a receipt or a code. broadcast is for newsletters and other mail to many people at once. Each lane has its own queue and its own reputation, so a newsletter never holds up or harms app mail, and the broadcast lane pauses itself when too many of its messages bounce or are reported as spam. A message sent to a paused lane is a 409 stream_paused.

On the broadcast lane, a message to one recipient that sets no List-Unsubscribe header of its own carries List-Unsubscribe and List-Unsubscribe-Post for one-click unsubscribe. Whoever uses it joins the unsubscribe list (GET /suppressions/unsubscribes), and broadcast lane mail is not sent to anybody on that list: the recipient is marked suppressed and email.suppressed says why. Transactional mail still reaches them.

One of"transactional""broadcast"
tagsRecord<string, string>
Default{}

SubjectSuggestionobject

objectstringRequired
One of"subject_suggestion"
subjectstringRequired

Trackingobject

Every /tracking endpoint returns this whole, its list included. It arrives trimmed in exactly one place, under tracking on a GET /emails row, where it is the counts half only: opens, clicks, opened, clicked, openCount, clickCount and firstOpenAt. A page of fifty sends each carrying its recipients and its links is a report nobody asked to have expanded. The trimmed form has no id on it either, so /emails/{id}/tracking rather than /tracking/{id} is the way back to the rest of it.

objectstring

Present when the report is the whole response body. Absent under an email's tracking field, which is part of that email rather than a resource in its own right.

One of"tracking"
idstring

The tracking record, tmsg_ + 24 hex. Not the message id and not the send id.

sendIdstring

The msg_ this went out as, when the send service handled it. Null for mail the mailbox agent sent on its own behalf, which is most composer, MCP and assistant traffic. Those messages do get a send record, but nothing links this tracking row to it. Tracking covers the mailbox rather than only the traffic that came through this API.

Can be null
threadIdstring
Can be null
messageIdstring

RFC 5322 Message-ID. Not a correlation key. The header is rewritten on the way out. Correlate on id.

Can be null
subjectstring
Can be null
fromstring
sourcestring
One of"api""oauth""composer""mcp""ai""form""mail-app""booking"
sentAtstring
Can be nullFormatdate-time
opensboolean

What was APPLIED to this message, resolved when it was sent from the setting of the address it was sent from (its own, else its domain catch-all's, else off, while a broadcast copy is on unless the broadcast or its address turned it off) and any per-send override, not what is switched on now. Turning tracking on today does not make yesterday's mail start reporting, and a report that implied otherwise would read as "nobody opened it".

clicksboolean

As opens, for link rewriting. The two are independent switches.

openedboolean
clickedboolean
attributableboolean

Whether every reading on this message can be pinned to a named recipient. False as soon as an unattributed copy has activity of its own, which is what happens whenever one body went to the whole list rather than a separate one per person. This is the flag that decides whether "Bob has not opened it" is a sentence a client is entitled to write, or whether all it may say is that somebody did. recipients carries the same fact one row at a time, and one row at a time is where it gets missed.

openCountinteger

Opens that looked like a person, with repeat fetches within thirty seconds collapsed. A preview pane redrawing is not a second reading. Through Gmail this is a floor and not a total: its proxy fetches the image once and caches it, so later readings never reach us.

clickCountinteger

Counted clicks. Stronger evidence than an open, and worth weighting as such: images are blocked far more often than links go unfollowed, so a message with clicks and no opens was certainly read.

openCountRawinteger

Every open hit, the automated ones included. openCountRaw - openCount is everything that was filtered out: Apple Mail Privacy Protection and corporate link scanners, which fetch on delivery whether or not a person ever looks, and alongside them the repeat fetches collapsed by the thirty-second window. Both are recorded and neither is counted, because discarding them outright would leave a gap in the log that nothing could explain. Do not read the difference as a machine count on its own. A message reopened twice in a minute lands in it too.

clickCountRawinteger

As openCountRaw, for clicks.

firstOpenAtstring
Can be nullFormatdate-time
lastOpenAtstring
Can be nullFormatdate-time
firstClickAtstring
Can be nullFormatdate-time
lastClickAtstring
Can be nullFormatdate-time
recipientsobject[]

One entry per tracked copy, which is not always one entry per person. Absent only from the trimmed form on a GET /emails row; every /tracking response carries it, list included.

emailstring

Null where the bytes could not be varied per person: an encrypted message, one too large to rebuild for each recipient, or a fallback carrier that takes the whole recipient list in a single call. The reading is real; which of the recipients did it is not knowable, and the only honest rendering is "someone on this message", never a name chosen out of the list.

Can be null
kindstring
Can be nullOne of"to""cc""bcc"
attributedboolean

False on exactly the rows described above. Branch on this rather than on email being a string, and show nothing where it is false: attributing an unattributed open to a named recipient invents evidence about a specific person.

openCountinteger
clickCountinteger
firstOpenAtstring
Can be nullFormatdate-time
lastOpenAtstring
Can be nullFormatdate-time
firstClickAtstring
Can be nullFormatdate-time
lastClickAtstring
Can be nullFormatdate-time
linksobject[]

The rewritten links, in the order they appeared in the message. Only links in the new part of the body are here: the quoted history under a reply belongs to whoever wrote it, and routing their URLs through our redirector would both rewrite their message and record the recipient "clicking" something we did not put there. Repeated destinations share one entry, because a campaign page linked from a header image, a button and a footer is one question asked three times. Absent only from the trimmed form on a GET /emails row.

idstring
urlstring

Where it actually goes: the original href.

labelstring

The text the link read as in the message, where it had any. A bare URL rarely tells the sender which of five links somebody followed.

Can be null
clickCountinteger
clickCountRawinteger

Translationobject

The preview, and exactly what a translate on POST /emails would produce for the same input. Show it, let someone edit it, then send the edited text as an ordinary html/subject with no translate on the request. Sending with translate after previewing translates a second time and discards the edits.

objectstring
One of"translation"
languageLanguage
detectedSourceLanguageLanguage
subjectstring

Null when no subject was given. Safe to put straight into a header.

Can be null
htmlstring

Null when no html was given. Carries dir="rtl" when the target needs it, and already contains the original beneath the translation when includeOriginal is on.

Can be null
textstring

Null when no text was given.

Can be null
includeOriginalboolean

Echoed because it changes what html contains: with it on, the original is already in there and appending your own copy would send it twice.