Skip to content

Emails

Base path: /api/v1/emails

POST /emails

Used by sync connectors to register incoming emails, or to create drafts/outgoing emails programmatically.

Body:

FieldTypeRequiredDefaultDescription
message_idstringNonullRFC 5322 Message-ID
thread_idstringNoautoAuto-resolved from references/in_reply_to if not provided
subjectstringNonull
from_addressstringYes
from_namestringNonull
to_addressesobject[]No[][{address, name}]
cc_addressesobject[]No[][{address, name}]
bcc_addressesobject[]No[][{address, name}] — only for outgoing/drafts
body_plainstringNonullPlain text version
body_htmlstringNonullHTML version
has_attachmentsbooleanNofalse
attachmentsobject[]No[][{filename, mime, size, path}]
folderstringNo"INBOX"e.g., INBOX, SENT, DRAFTS, TRASH, SPAM, OUTBOX, SNOOZED
is_readbooleanNofalse
is_starredbooleanNofalse
received_atdatetimeConditionalRequired if status='received'
account_idUUIDNonullEmail account reference
statusstringNo"received"See Status values
in_reply_tostringNonullMessage-ID of the replied email
referencesstring[]No[]Thread Message-ID chain (RFC 5322)
labelsstring[]No[]Additional labels
custom_fieldsobjectNonullArbitrary key-value data

Response: 201 Created with the full email object.

WebSocket event: email.created

GET /emails

Query parameters:

ParameterTypeDefaultDescription
limitinteger20
offsetinteger0
sortstringreceived_atValid: received_at, created_at, subject
orderstringdesc
searchstringFull-text search on subject, from_address, from_name, body
folderstringFilter by folder
is_readboolean
is_starredboolean
has_attachmentsboolean
thread_idstringGet all emails in a thread
fromdatetimeEmails received from this date
todatetimeEmails received until this date
account_idUUIDFilter by email account
statusstringFilter by status
labelstringFilter by label
snoozedbooleantrue: only snoozed. false: exclude snoozed
include_deletedbooleanfalse
only_deletedbooleanfalse

Response: 200 OK with list envelope.

Supports ?format=compact for token-optimized output.

GET /emails/:id

Response: 200 OK with single record envelope.

PATCH /emails/:id

Updatable fields: folder, is_read, is_starred, custom_fields, labels

Response: 200 OK with the full updated email.

WebSocket event: email.updated

PATCH /emails/:id/custom

Incremental update via shallow merge. Same behavior as Notes custom fields.

Response: 200 OK with the full email.

WebSocket event: email.updated

POST /emails/send

Creates and queues an email for sending in a single step.

Body:

FieldTypeRequiredDefaultDescription
account_idUUIDNodefault accountUses the account with is_default=true if omitted
to_addressesobject[]YesAt least one recipient. [{address, name}]
cc_addressesobject[]No[]
bcc_addressesobject[]No[]
subjectstringNonull
body_plainstringConditionalnullAt least body_plain or body_html required
body_htmlstringConditionalnull
in_reply_tostringNonullMessage-ID of the email being replied to
attachmentsUUID[]No[]Array of file IDs
read_receipt_requestedbooleanNofalse
scheduled_atdatetimeNonullIf set, schedules the email for future sending

Response: 202 Accepted

WebSocket event: email.queued (or email.scheduled if scheduled_at is set)

Errors:

HTTPCodeWhen
422NO_DEFAULT_ACCOUNTNo account_id and no default account configured
422ACCOUNT_NOT_CONNECTEDAccount is not in connected status

POST /emails/drafts

Creates a draft with minimal fields.

Body:

FieldTypeRequiredDescription
account_idUUIDNoUses default account if omitted
tostringNoPrimary recipient
ccstringNo
bccstringNo
subjectstringNo
body_plainstringNo
body_htmlstringNo
attachmentsUUID[]NoArray of file IDs
in_reply_tostringNoMessage-ID of replied email

Response: 201 Created

WebSocket event: email.created

POST /emails/:id/send

Queues an existing draft for sending. The email must have status of draft or failed (manual retry).

Response: 202 Accepted

WebSocket event: email.queued

Errors:

HTTPCodeWhen
409NOT_A_DRAFTEmail is not a draft or failed
409ALREADY_SENTEmail was already sent

POST /emails/:id/snooze

Body:

{
"until": "2026-02-20T09:00:00Z"
}

Moves the email to SNOOZED folder and schedules it to return to its original folder at the specified time.

Response: 200 OKWebSocket event: email.snoozed


POST /emails/:id/unsnooze

Returns the email to its original folder immediately.

Response: 200 OKWebSocket event: email.unsnoozed

Error: 409 NOT_SNOOZED if the email is not snoozed.

  • POST /emails/:id/read — Mark as read
  • POST /emails/:id/unread — Mark as unread
  • POST /emails/:id/star — Star
  • POST /emails/:id/unstar — Unstar

All return 200 OK and emit email.updated.

POST /emails/batch

Body:

FieldTypeRequiredDescription
idsUUID[]YesMaximum 100
actionstringYesSee actions below
paramsobjectConditionalRequired for move, add_label, remove_label, snooze

Actions:

ActionParamsDescription
mark_readMark as read
mark_unreadMark as unread
starStar
unstarUnstar
move{ "folder": "..." }Move to folder
add_label{ "label": "..." }Add label
remove_label{ "label": "..." }Remove label
deleteSoft delete
restoreRestore from trash
snooze{ "until": "datetime" }Batch snooze

Response: 200 OK with { processed, failed, errors }.

DELETE /emails/:id — soft delete

DELETE /emails/:id?permanent=true — permanent delete (irreversible)

See Soft Deletes for details.

WebSocket event: email.deleted

POST /emails/:id/restore

WebSocket event: email.restored

StatusDescriptionTypical folder
receivedIncoming email synced via IMAPINBOX
draftDraft created by userDRAFTS
queuedReady to send, in queueOUTBOX
sendingCurrently being sentOUTBOX
sentSuccessfully sentSENT
failedSend failed after retriesOUTBOX