Mail

Mailboxes, messages, threads, search, attachments and sending. Everything is addressed under an account.

Guides: Read mail, Sending mail, Drafts, Mail merge.

MethodPathOperationScope
GET/v1/accounts/{accountId}/mailboxesList mailboxes (folders or labels)read
POST/v1/accounts/{accountId}/mailboxesCreate a mailbox (folder or label)write
PATCH/v1/accounts/{accountId}/mailboxes/{mailboxId}Rename or move a mailboxwrite
DELETE/v1/accounts/{accountId}/mailboxes/{mailboxId}Delete a mailboxwrite
GET/v1/accounts/{accountId}/messagesList messages, newest firstread
GET/v1/accounts/{accountId}/messages/{messageId}Get a message with its bodyread
PATCH/v1/accounts/{accountId}/messages/{messageId}Change flags, move, or change labelswrite
DELETE/v1/accounts/{accountId}/messages/{messageId}Delete a messagewrite
GET/v1/accounts/{accountId}/messages/{messageId}/rawDownload the message as RFC 5322 (message/rfc822)read
GET/v1/accounts/{accountId}/messages/{messageId}/attachments/{attachmentId}Download an attachmentread
GET/v1/accounts/{accountId}/search/messagesSearch messagesread
GET/v1/accounts/{accountId}/threadsList threads, latest activity firstread
GET/v1/accounts/{accountId}/threads/{threadId}Get a thread with its messages, oldest firstread
PATCH/v1/accounts/{accountId}/threads/{threadId}Mark a thread read or unread, or move itwrite
DELETE/v1/accounts/{accountId}/threads/{threadId}Move a thread to Trashwrite
POST/v1/accounts/{accountId}/draftsCreate a draftwrite
PUT/v1/accounts/{accountId}/drafts/{messageId}Replace a draft's contentwrite
DELETE/v1/accounts/{accountId}/drafts/{messageId}Delete a draftwrite
POST/v1/accounts/{accountId}/drafts/{messageId}/sendSend a draftsend
GET/v1/accounts/{accountId}/outboxList queued and recent sends, newest firstread
POST/v1/accounts/{accountId}/outboxSend a messagesend
GET/v1/accounts/{accountId}/outbox/{outboxId}Get a sendread
PATCH/v1/accounts/{accountId}/outbox/{outboxId}Reschedule a queued sendsend
DELETE/v1/accounts/{accountId}/outbox/{outboxId}Cancel a queued sendsend
GET/v1/accounts/{accountId}/outbox/{outboxId}/trackingList a send's opens and clicksread
POST/v1/accounts/{accountId}/mail-mergesSend one message per recipient (mail merge)send
GET
/v1/accounts/{accountId}/mailboxes

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

Required scope: read.

Path Parameters

accountId*string
Formatuuid

Response Body

OK

application/json
  1. response
items*array<>
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/mailboxes'
Example Response
{  "items": [    {      "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",      "name": "string",      "path": "string",      "parentId": "1adf657e-5e25-4548-989a-ccbcf88b0d04",      "role": "inbox",      "kind": "folder",      "totalCount": 0,      "unreadCount": 0    }  ]}
POST
/v1/accounts/{accountId}/mailboxes

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Microsoft and IMAP create a folder; Gmail creates a label (nested with '/' in the label name when parentId is set). A folder that is not empty can be deleted later: Microsoft deletes the folder and the messages in it, Gmail removes the label and leaves the messages, IMAP deletes the mailbox and its messages and refuses a mailbox that still has child mailboxes (provider_rejected).

Required scope: write.

Path Parameters

accountId*string
Formatuuid

Request Body

application/json
  1. body
name*string
Length1 <= length <= 255
parentId?string
Formatuuid

Response Body

OK

application/json
  1. response
id*string
Formatuuid
name*string
path*string
parentId*|null
role*MailboxRole
Value in"inbox""sent""drafts""trash""junk""archive""all""important""custom"
kind*MailboxKind
Value in"folder""label"
totalCount*integer|null
unreadCount*integer|null
Example Request
curl -X POST 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/mailboxes' \  -H 'Content-Type: application/json' \  -d '{  "name": "string"}'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "name": "string",  "path": "string",  "parentId": "1adf657e-5e25-4548-989a-ccbcf88b0d04",  "role": "inbox",  "kind": "folder",  "totalCount": 0,  "unreadCount": 0}

Rename or move a mailbox

PATCH
/v1/accounts/{accountId}/mailboxes/{mailboxId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Refused for system and special-use mailboxes (Inbox, Sent, Drafts, Trash, Junk, Archive, Gmail system labels) with mailbox_protected. parentId null moves it to the top level. On Gmail the label's name keeps '/' between a parent label and this one.

Required scope: write.

Path Parameters

accountId*string
Formatuuid
mailboxId*string
Formatuuid

Request Body

application/json
  1. body
name?string
Length1 <= length <= 255
parentId?|null

Response Body

OK

application/json
  1. response
id*string
Formatuuid
name*string
path*string
parentId*|null
role*MailboxRole
Value in"inbox""sent""drafts""trash""junk""archive""all""important""custom"
kind*MailboxKind
Value in"folder""label"
totalCount*integer|null
unreadCount*integer|null
Example Request
curl -X PATCH 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/mailboxes/8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "name": "string",  "path": "string",  "parentId": "1adf657e-5e25-4548-989a-ccbcf88b0d04",  "role": "inbox",  "kind": "folder",  "totalCount": 0,  "unreadCount": 0}

Delete a mailbox

DELETE
/v1/accounts/{accountId}/mailboxes/{mailboxId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Refused for system and special-use mailboxes (mailbox_protected). Microsoft deletes the folder and the messages in it (child folders are refused, provider_rejected). Gmail deletes the label and takes it off its messages, which stay. IMAP deletes the mailbox and its messages; a mailbox that still has children is refused by the server (provider_rejected). The index drops the mailbox now; messages it held leave on the next sync, the same way a deletion made elsewhere does.

Required scope: write.

Path Parameters

accountId*string
Formatuuid
mailboxId*string
Formatuuid

Response Body

OK

application/json
  1. response
Example Request
curl -X DELETE 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/mailboxes/8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a'
Example Response
null
GET
/v1/accounts/{accountId}/messages

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

Covers the account's sync window. Without mailboxId, lists every mailbox. importance keeps the messages with that priority; messages indexed before priorities were read (importance null) match none. from and to match part of an address or display name, case-insensitive (to looks at To, Cc and Bcc); after and before bound receivedAt.

Required scope: read.

Path Parameters

accountId*string
Formatuuid

Query Parameters

mailboxId?string
Formatuuid
from?string
Length1 <= length <= 320
to?string
Length1 <= length <= 320
unread?boolean|
flagged?boolean|
hasAttachments?boolean|
importance?string
Value in"high""normal""low"
after?string
Formatdate-time
before?string
Formatdate-time
limit?integer
Range1 <= value <= 200
Default50
cursor?string
Length1 <= length <= 512

Response Body

OK

application/json
  1. response
items*array<>
nextCursor*|null
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/messages'
Example Response
{  "items": [    {      "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",      "threadId": "18f2a3b4c5d6e7f8",      "internetMessageId": "<CAF3x9a@mail.example.org>",      "mailboxIds": [        "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"      ],      "subject": "Quarterly report",      "from": {        "name": "Ada Lovelace",        "address": "ada@example.com"      },      "to": [        {          "name": "Ada Lovelace",          "address": "ada@example.com"        }      ],      "cc": [        {          "name": "Ada Lovelace",          "address": "ada@example.com"        }      ],      "snippet": "Hi Ada, the numbers for the quarter are attached.",      "receivedAt": "2026-05-04T09:30:00.000Z",      "sentAt": "2026-05-04T09:30:00.000Z",      "isRead": true,      "isFlagged": true,      "isDraft": true,      "hasAttachments": true,      "categories": [        "string"      ],      "direction": "inbound",      "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",      "reference": "string",      "importance": "high"    }  ],  "nextCursor": "eyJ0eG5pZCI6IjQyIiwiaWQiOjEwMDF9"}
GET
/v1/accounts/{accountId}/messages/{messageId}

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

With bodyStorage cache or full the body comes from stored content (cache fetches and stores the raw message on a miss), and attachment ids are MIME part numbers ("1.2"); otherwise it is read from the provider. Drafts are always read from the provider.

Required scope: read.

Path Parameters

accountId*string
Formatuuid
messageId*string
Formatuuid

Response Body

OK

application/json
  1. response
id*string
Formatuuid
threadId*|null
internetMessageId*string|null
mailboxIds*array<>
subject*string|null
from*|null
to*array<>
cc*array<>
snippet*string|null
receivedAt*|null
sentAt*|null
isRead*boolean
isFlagged*boolean
isDraft*boolean
hasAttachments*boolean
categories*array<string>
direction*MessageDirection
Value in"inbound""outbound"
outboxId*|null
reference*string|null
importance*|null
bcc*array<>
replyTo*array<>
body*
attachments*array<>
classification*|null
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/messages/c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "threadId": "18f2a3b4c5d6e7f8",  "internetMessageId": "<CAF3x9a@mail.example.org>",  "mailboxIds": [    "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"  ],  "subject": "Quarterly report",  "from": {    "name": "Ada Lovelace",    "address": "ada@example.com"  },  "to": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "cc": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "snippet": "Hi Ada, the numbers for the quarter are attached.",  "receivedAt": "2026-05-04T09:30:00.000Z",  "sentAt": "2026-05-04T09:30:00.000Z",  "isRead": true,  "isFlagged": true,  "isDraft": true,  "hasAttachments": true,  "categories": [    "string"  ],  "direction": "inbound",  "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",  "reference": "string",  "importance": "high",  "bcc": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "replyTo": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "body": {    "text": "string",    "html": "string"  },  "attachments": [    {      "id": "string",      "filename": "string",      "contentType": "string",      "size": 0,      "isInline": true,      "contentId": "string"    }  ],  "classification": {    "kind": "opt_out",    "probabilities": {      "property1": 0,      "property2": 0    },    "optOutProbability": 0,    "autoReplyCategory": "out_of_office",    "returnDate": "2026-05-04",    "alternateContacts": [      "string"    ],    "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",    "model": "string",    "classifiedAt": "2026-05-04T09:30:00.000Z"  }}
PATCH
/v1/accounts/{accountId}/messages/{messageId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

mailboxId moves the message (Microsoft folders, or Gmail: replaces the message's folder-like labels). addMailboxIds/removeMailboxIds apply Gmail labels.

Required scope: write.

Path Parameters

accountId*string
Formatuuid
messageId*string
Formatuuid

Request Body

application/json
  1. body
isRead?boolean
isFlagged?boolean
mailboxId?string
Formatuuid
addMailboxIds?array<>
Itemsitems <= 50
removeMailboxIds?array<>
Itemsitems <= 50

Response Body

OK

application/json
  1. response
id*string
Formatuuid
threadId*|null
internetMessageId*string|null
mailboxIds*array<>
subject*string|null
from*|null
to*array<>
cc*array<>
snippet*string|null
receivedAt*|null
sentAt*|null
isRead*boolean
isFlagged*boolean
isDraft*boolean
hasAttachments*boolean
categories*array<string>
direction*MessageDirection
Value in"inbound""outbound"
outboxId*|null
reference*string|null
importance*|null
Example Request
curl -X PATCH 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/messages/c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "threadId": "18f2a3b4c5d6e7f8",  "internetMessageId": "<CAF3x9a@mail.example.org>",  "mailboxIds": [    "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"  ],  "subject": "Quarterly report",  "from": {    "name": "Ada Lovelace",    "address": "ada@example.com"  },  "to": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "cc": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "snippet": "Hi Ada, the numbers for the quarter are attached.",  "receivedAt": "2026-05-04T09:30:00.000Z",  "sentAt": "2026-05-04T09:30:00.000Z",  "isRead": true,  "isFlagged": true,  "isDraft": true,  "hasAttachments": true,  "categories": [    "string"  ],  "direction": "inbound",  "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",  "reference": "string",  "importance": "high"}

Delete a message

DELETE
/v1/accounts/{accountId}/messages/{messageId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Moves it to Trash; ?permanent=true deletes it outright.

Required scope: write.

Path Parameters

accountId*string
Formatuuid
messageId*string
Formatuuid

Query Parameters

permanent?|
Defaultfalse

Response Body

OK

application/json
  1. response
Example Request
curl -X DELETE 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/messages/c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b'
Example Response
null
GET
/v1/accounts/{accountId}/messages/{messageId}/raw

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

Served from stored content when the account stores bodies.

Required scope: read.

Path Parameters

accountId*string
Formatuuid
messageId*string
Formatuuid

Response Body

The message as message/rfc822

response?stringmessage/rfc822
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/messages/c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b/raw'
Example Response
"string"

Download an attachment

GET
/v1/accounts/{accountId}/messages/{messageId}/attachments/{attachmentId}

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

Served from stored content when the account stores bodies; an id the stored message does not have is asked of the provider, which answers provider_not_found when it has no such attachment either.

Required scope: read.

Path Parameters

accountId*string
Formatuuid
messageId*string
Formatuuid
attachmentId*string
Length1 <= length <= 1024

Response Body

The attachment, with the Content-Type it was sent with

response?string*/*
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/messages/c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b/attachments/string'
Example Response
"string"

Search messages

GET
/v1/accounts/{accountId}/search/messages

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

source=index searches the index with full-text search (q in web search syntax: words, "phrases", OR, -word) over subject, sender and recipient names and addresses, the snippet, and the body text of stored messages (bodyStorage cache or full), best matches first. source=provider passes q to the provider unchanged (Gmail search syntax, Microsoft KQL, IMAP SEARCH TEXT), newest first; results the index holds carry their id, others id null. source=auto (default) asks the provider when the index cannot cover the request: after lies before the sync window, or the index found nothing while it does not hold everything (the account has a sync window, or bodyStorage is not full). Otherwise, and whenever the provider cannot be asked (inactive account, rate limit, provider error), the index answers. from, to, unread, flagged and hasAttachments narrow either source's results as on messages.list; a provider page they narrow can hold fewer than limit results while nextCursor goes on. The response names the source that answered; nextCursor stays with it. A provider search of a Gmail account counts against the account's Gmail searches per minute; past them it answers rate_limited with Retry-After.

Required scope: read.

Path Parameters

accountId*string
Formatuuid

Query Parameters

q*string
Length1 <= length <= 500
mailboxId?string
Formatuuid
from?string
Length1 <= length <= 320
to?string
Length1 <= length <= 320
unread?boolean|
flagged?boolean|
hasAttachments?boolean|
after?string
Formatdate-time
before?string
Formatdate-time
source?string
Default"auto"
Value in"auto""index""provider"
limit?integer
Range1 <= value <= 100
Default25
cursor?string
Length1 <= length <= 512

Response Body

OK

application/json
  1. response
items*array<>
nextCursor*|null
source*SearchResultSource
Value in"index""provider"
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/search/messages?q=invoice'
Example Response
{  "items": [    {      "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",      "threadId": "18f2a3b4c5d6e7f8",      "internetMessageId": "<CAF3x9a@mail.example.org>",      "mailboxIds": [        "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"      ],      "subject": "Quarterly report",      "from": {        "name": "Ada Lovelace",        "address": "ada@example.com"      },      "to": [        {          "name": "Ada Lovelace",          "address": "ada@example.com"        }      ],      "cc": [        {          "name": "Ada Lovelace",          "address": "ada@example.com"        }      ],      "snippet": "Hi Ada, the numbers for the quarter are attached.",      "receivedAt": "2026-05-04T09:30:00.000Z",      "sentAt": "2026-05-04T09:30:00.000Z",      "isRead": true,      "isFlagged": true,      "isDraft": true,      "hasAttachments": true,      "categories": [        "string"      ],      "direction": "inbound",      "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",      "reference": "string",      "importance": "high"    }  ],  "nextCursor": "eyJ0eG5pZCI6IjQyIiwiaWQiOjEwMDF9",  "source": "index"}
GET
/v1/accounts/{accountId}/threads

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

Covers the account's sync window. With mailboxId, lists threads with a message in that mailbox, ordered by their latest message there; each summary still describes the whole thread.

Required scope: read.

Path Parameters

accountId*string
Formatuuid

Query Parameters

mailboxId?string
Formatuuid
limit?integer
Range1 <= value <= 100
Default25
cursor?string
Length1 <= length <= 512

Response Body

OK

application/json
  1. response
items*array<>
nextCursor*|null
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/threads'
Example Response
{  "items": [    {      "id": "string",      "subject": "Quarterly report",      "snippet": "Hi Ada, the numbers for the quarter are attached.",      "participants": [        {          "name": "Ada Lovelace",          "address": "ada@example.com"        }      ],      "messageCount": 0,      "unreadCount": 0,      "isFlagged": true,      "hasAttachments": true,      "hasDrafts": true,      "mailboxIds": [        "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"      ],      "latestMessageId": "3e8ac85f-c412-433a-a738-8be7894e4050",      "lastMessageAt": "2026-05-04T09:30:00.000Z"    }  ],  "nextCursor": "eyJ0eG5pZCI6IjQyIiwiaWQiOjEwMDF9"}
GET
/v1/accounts/{accountId}/threads/{threadId}

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

Required scope: read.

Path Parameters

accountId*string
Formatuuid
threadId*string
Length1 <= length <= 1024

Response Body

OK

application/json
  1. response
id*string
Length1 <= length <= 1024
subject*string|null
snippet*string|null
participants*array<>
messageCount*integer
unreadCount*integer
isFlagged*boolean
hasAttachments*boolean
hasDrafts*boolean
mailboxIds*array<>
latestMessageId*string
Formatuuid
lastMessageAt*|null
messages*array<>
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/threads/18f2a3b4c5d6e7f8'
Example Response
{  "id": "string",  "subject": "Quarterly report",  "snippet": "Hi Ada, the numbers for the quarter are attached.",  "participants": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "messageCount": 0,  "unreadCount": 0,  "isFlagged": true,  "hasAttachments": true,  "hasDrafts": true,  "mailboxIds": [    "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"  ],  "latestMessageId": "3e8ac85f-c412-433a-a738-8be7894e4050",  "lastMessageAt": "2026-05-04T09:30:00.000Z",  "messages": [    {      "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",      "threadId": "18f2a3b4c5d6e7f8",      "internetMessageId": "<CAF3x9a@mail.example.org>",      "mailboxIds": [        "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"      ],      "subject": "Quarterly report",      "from": {        "name": "Ada Lovelace",        "address": "ada@example.com"      },      "to": [        {          "name": "Ada Lovelace",          "address": "ada@example.com"        }      ],      "cc": [        {          "name": "Ada Lovelace",          "address": "ada@example.com"        }      ],      "snippet": "Hi Ada, the numbers for the quarter are attached.",      "receivedAt": "2026-05-04T09:30:00.000Z",      "sentAt": "2026-05-04T09:30:00.000Z",      "isRead": true,      "isFlagged": true,      "isDraft": true,      "hasAttachments": true,      "categories": [        "string"      ],      "direction": "inbound",      "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",      "reference": "string",      "importance": "high"    }  ]}
PATCH
/v1/accounts/{accountId}/threads/{threadId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Applies the change to every message of the thread through the message operations; messages already in that state are skipped. mailboxId moves each message there (Gmail: replaces its folder-like labels). A failure part way leaves the messages done so far changed; repeating the call finishes the rest.

Required scope: write.

Path Parameters

accountId*string
Formatuuid
threadId*string
Length1 <= length <= 1024

Request Body

application/json
  1. body
isRead?boolean
mailboxId?string
Formatuuid

Response Body

OK

application/json
  1. response
id*string
Length1 <= length <= 1024
subject*string|null
snippet*string|null
participants*array<>
messageCount*integer
unreadCount*integer
isFlagged*boolean
hasAttachments*boolean
hasDrafts*boolean
mailboxIds*array<>
latestMessageId*string
Formatuuid
lastMessageAt*|null
messages*array<>
Example Request
curl -X PATCH 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/threads/18f2a3b4c5d6e7f8' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "id": "string",  "subject": "Quarterly report",  "snippet": "Hi Ada, the numbers for the quarter are attached.",  "participants": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "messageCount": 0,  "unreadCount": 0,  "isFlagged": true,  "hasAttachments": true,  "hasDrafts": true,  "mailboxIds": [    "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"  ],  "latestMessageId": "3e8ac85f-c412-433a-a738-8be7894e4050",  "lastMessageAt": "2026-05-04T09:30:00.000Z",  "messages": [    {      "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",      "threadId": "18f2a3b4c5d6e7f8",      "internetMessageId": "<CAF3x9a@mail.example.org>",      "mailboxIds": [        "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"      ],      "subject": "Quarterly report",      "from": {        "name": "Ada Lovelace",        "address": "ada@example.com"      },      "to": [        {          "name": "Ada Lovelace",          "address": "ada@example.com"        }      ],      "cc": [        {          "name": "Ada Lovelace",          "address": "ada@example.com"        }      ],      "snippet": "Hi Ada, the numbers for the quarter are attached.",      "receivedAt": "2026-05-04T09:30:00.000Z",      "sentAt": "2026-05-04T09:30:00.000Z",      "isRead": true,      "isFlagged": true,      "isDraft": true,      "hasAttachments": true,      "categories": [        "string"      ],      "direction": "inbound",      "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",      "reference": "string",      "importance": "high"    }  ]}

Move a thread to Trash

DELETE
/v1/accounts/{accountId}/threads/{threadId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Moves every message of the thread that is not already there to Trash. A failure part way leaves the messages done so far changed; repeating the call finishes the rest.

Required scope: write.

Path Parameters

accountId*string
Formatuuid
threadId*string
Length1 <= length <= 1024

Response Body

OK

application/json
  1. response
Example Request
curl -X DELETE 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/threads/18f2a3b4c5d6e7f8'
Example Response
null

Create a draft

POST
/v1/accounts/{accountId}/drafts

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Content is rendered when the request is taken, so later template edits never change it.

Required scope: write.

Path Parameters

accountId*string
Formatuuid

Request Body

application/json
  1. body
to?array<>
Itemsitems <= 500
Default[]
cc?array<>
Itemsitems <= 500
Default[]
bcc?array<>
Itemsitems <= 500
Default[]
replyTo?array<>
Itemsitems <= 10
Default[]
subject?string
Lengthlength <= 998
text?string
Lengthlength <= 2097152
html?string
Lengthlength <= 2097152
attachments?array<>
Itemsitems <= 50
Default[]
inReplyToMessageId?string
Formatuuid
from?
signature?boolean
priority?MessagePriority
Default"normal"
Value in"high""normal""low"
template?string
Formatuuid
render?
previewText?string
Lengthlength <= 2000
format?TemplateFormat
Value in"html""markdown"

Response Body

OK

application/json
  1. response
id*string
Formatuuid
threadId*|null
internetMessageId*string|null
mailboxIds*array<>
subject*string|null
from*|null
to*array<>
cc*array<>
snippet*string|null
receivedAt*|null
sentAt*|null
isRead*boolean
isFlagged*boolean
isDraft*boolean
hasAttachments*boolean
categories*array<string>
direction*MessageDirection
Value in"inbound""outbound"
outboxId*|null
reference*string|null
importance*|null
Example Request
curl -X POST 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/drafts' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "threadId": "18f2a3b4c5d6e7f8",  "internetMessageId": "<CAF3x9a@mail.example.org>",  "mailboxIds": [    "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"  ],  "subject": "Quarterly report",  "from": {    "name": "Ada Lovelace",    "address": "ada@example.com"  },  "to": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "cc": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "snippet": "Hi Ada, the numbers for the quarter are attached.",  "receivedAt": "2026-05-04T09:30:00.000Z",  "sentAt": "2026-05-04T09:30:00.000Z",  "isRead": true,  "isFlagged": true,  "isDraft": true,  "hasAttachments": true,  "categories": [    "string"  ],  "direction": "inbound",  "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",  "reference": "string",  "importance": "high"}

Delete a draft

DELETE
/v1/accounts/{accountId}/drafts/{messageId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Required scope: write.

Path Parameters

accountId*string
Formatuuid
messageId*string
Formatuuid

Response Body

OK

application/json
  1. response
Example Request
curl -X DELETE 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/drafts/c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b'
Example Response
null
PUT
/v1/accounts/{accountId}/drafts/{messageId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Content is rendered when the request is taken, so later template edits never change it.

Required scope: write.

Path Parameters

accountId*string
Formatuuid
messageId*string
Formatuuid

Request Body

application/json
  1. body
to?array<>
Itemsitems <= 500
Default[]
cc?array<>
Itemsitems <= 500
Default[]
bcc?array<>
Itemsitems <= 500
Default[]
replyTo?array<>
Itemsitems <= 10
Default[]
subject?string
Lengthlength <= 998
text?string
Lengthlength <= 2097152
html?string
Lengthlength <= 2097152
attachments?array<>
Itemsitems <= 50
Default[]
inReplyToMessageId?string
Formatuuid
from?
signature?boolean
priority?MessagePriority
Default"normal"
Value in"high""normal""low"
template?string
Formatuuid
render?
previewText?string
Lengthlength <= 2000
format?TemplateFormat
Value in"html""markdown"

Response Body

OK

application/json
  1. response
id*string
Formatuuid
threadId*|null
internetMessageId*string|null
mailboxIds*array<>
subject*string|null
from*|null
to*array<>
cc*array<>
snippet*string|null
receivedAt*|null
sentAt*|null
isRead*boolean
isFlagged*boolean
isDraft*boolean
hasAttachments*boolean
categories*array<string>
direction*MessageDirection
Value in"inbound""outbound"
outboxId*|null
reference*string|null
importance*|null
Example Request
curl -X PUT 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/drafts/c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "threadId": "18f2a3b4c5d6e7f8",  "internetMessageId": "<CAF3x9a@mail.example.org>",  "mailboxIds": [    "8a3f5c7e-2d4b-4c6e-9f1a-3b5d7f9e1c2a"  ],  "subject": "Quarterly report",  "from": {    "name": "Ada Lovelace",    "address": "ada@example.com"  },  "to": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "cc": [    {      "name": "Ada Lovelace",      "address": "ada@example.com"    }  ],  "snippet": "Hi Ada, the numbers for the quarter are attached.",  "receivedAt": "2026-05-04T09:30:00.000Z",  "sentAt": "2026-05-04T09:30:00.000Z",  "isRead": true,  "isFlagged": true,  "isDraft": true,  "hasAttachments": true,  "categories": [    "string"  ],  "direction": "inbound",  "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",  "reference": "string",  "importance": "high"}

Send a draft

POST
/v1/accounts/{accountId}/drafts/{messageId}/send

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

A message without a sendAt later than the account's undo window (undoSendSeconds) from now waits out the window; undoUntil says until when cancelling the entry (DELETE .../outbox/{outboxId}) undoes the send. With tracking, the HTML part gets a 1x1 pixel (opens) and its http(s) links point at Workwae's redirect (clicks), reported as message.opened and message.clicked and counted on the outbox entry; text parts are left as they are. With clicks, tracking.honeypot (else the account's trackingHoneypot) also adds one hidden link only machines follow; it does nothing without clicks, so asking for it with clicks off is refused rather than ignored. Tracking is per message, not per recipient: every recipient gets the same MIME. A Gmail or Microsoft 365 draft is sent by the provider as it is stored, so it is never tracked (the account's defaults do not apply); an IMAP draft is. A cancelled draft-send leaves the draft in place.

Required scope: send.

Path Parameters

accountId*string
Formatuuid
messageId*string
Formatuuid

Request Body

application/json
  1. body
sendAt?string
Formatdate-time
reference?string
Lengthlength <= 255
tracking?

Response Body

OK

application/json
  1. response
id*string
Formatuuid
accountId*string
Formatuuid
status*OutboxStatus
Value in"queued""sending""sent""failed""cancelled"
reference*string|null
sendAt*string
Formatdate-time
attempt*integer
lastError*string|null
providerMessageId*|null
internetMessageId*string|null
sentAt*|null
createdAt*string
Formatdate-time
trackOpens*boolean
trackClicks*boolean
trackHoneypot*boolean
humanOpenCount*integer
machineOpenCount*integer
humanClickCount*integer
machineClickCount*integer
firstOpenedAt*|null
lastOpenedAt*|null
undoUntil*|null
Example Request
curl -X POST 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/drafts/c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b/send' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "accountId": "6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",  "status": "queued",  "reference": "string",  "sendAt": "2026-05-04T09:30:00.000Z",  "attempt": 0,  "lastError": "string",  "providerMessageId": "string",  "internetMessageId": "<CAF3x9a@mail.example.org>",  "sentAt": "2026-05-04T09:30:00.000Z",  "createdAt": "2026-05-04T09:30:00.000Z",  "trackOpens": true,  "trackClicks": true,  "trackHoneypot": true,  "humanOpenCount": 0,  "machineOpenCount": 0,  "humanClickCount": 0,  "machineClickCount": 0,  "firstOpenedAt": "2026-05-04T09:30:00.000Z",  "lastOpenedAt": "2026-05-04T09:30:00.000Z",  "undoUntil": "2026-05-04T09:30:00.000Z"}
GET
/v1/accounts/{accountId}/outbox

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

status keeps the entries in that status.

Required scope: read.

Path Parameters

accountId*string
Formatuuid

Query Parameters

status?string
Value in"queued""sending""sent""failed""cancelled"
limit?integer
Range1 <= value <= 200
Default50
cursor?string
Length1 <= length <= 512

Response Body

OK

application/json
  1. response
items*array<>
nextCursor*|null
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/outbox'
Example Response
{  "items": [    {      "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",      "accountId": "6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",      "status": "queued",      "reference": "string",      "sendAt": "2026-05-04T09:30:00.000Z",      "attempt": 0,      "lastError": "string",      "providerMessageId": "string",      "internetMessageId": "<CAF3x9a@mail.example.org>",      "sentAt": "2026-05-04T09:30:00.000Z",      "createdAt": "2026-05-04T09:30:00.000Z",      "trackOpens": true,      "trackClicks": true,      "trackHoneypot": true,      "humanOpenCount": 0,      "machineOpenCount": 0,      "humanClickCount": 0,      "machineClickCount": 0,      "firstOpenedAt": "2026-05-04T09:30:00.000Z",      "lastOpenedAt": "2026-05-04T09:30:00.000Z"    }  ],  "nextCursor": "eyJ0eG5pZCI6IjQyIiwiaWQiOjEwMDF9"}

Send a message

POST
/v1/accounts/{accountId}/outbox

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Queues the message as an outbox entry; delivery is reported with send.sent, send.retrying and send.failed events carrying its id as outboxId, and a later bounce with message.bounced. Delivery is at-least-once, except outcome_unknown (below), and without order across retries: an account's entries are first tried in queue order, but one backing off after a failed attempt does not hold up the later ones. A retry first looks for a copy that already went out. When no SMTP reply comes after the final dot was handed to the operating system (the connection dropped or timed out, or the attempt ran out of time), the server may have accepted the message, so it is not sent again: send.failed carries code outcome_unknown and the client app decides whether to resend. A message without a sendAt later than the account's undo window (undoSendSeconds) from now waits out the window; undoUntil says until when cancelling the entry (DELETE .../outbox/{outboxId}) undoes the send. With tracking, the HTML part gets a 1x1 pixel (opens) and its http(s) links point at Workwae's redirect (clicks), reported as message.opened and message.clicked and counted on the outbox entry; text parts are left as they are. With clicks, tracking.honeypot (else the account's trackingHoneypot) also adds one hidden link only machines follow; it does nothing without clicks, so asking for it with clicks off is refused rather than ignored. Tracking is per message, not per recipient: every recipient gets the same MIME. With listId, content is always rendered, {{rcpt.unsubscribeUrl}}, {{rcpt.address}} and {{rcpt.name}} are the recipient's, and the message carries List-Unsubscribe with that same URL and List-Unsubscribe-Post: List-Unsubscribe=One-Click (RFC 8058). Click tracking never rewrites a List-Unsubscribe URL, so the body's link stays as written. A recipient on that list or on the global list is not sent to, at queue time and again at delivery (send.failed with code suppressed). Without listId no list is consulted. Whether the provider's DKIM signature covers the List-Unsubscribe headers is up to its signing. For one message per recipient, see POST .../mail-merges. Content is rendered when the request is taken, so later template edits never change it. An unknown field is refused.

Required scope: send.

Path Parameters

accountId*string
Formatuuid

Request Body

application/json
  1. body
to?array<>
Itemsitems <= 500
Default[]
cc?array<>
Itemsitems <= 500
Default[]
bcc?array<>
Itemsitems <= 500
Default[]
replyTo?array<>
Itemsitems <= 10
Default[]
subject?string
Lengthlength <= 998
text?string
Lengthlength <= 2097152
html?string
Lengthlength <= 2097152
attachments?array<>
Itemsitems <= 50
Default[]
inReplyToMessageId?string
Formatuuid
from?
signature?boolean
priority?MessagePriority
Default"normal"
Value in"high""normal""low"
template?string
Formatuuid
render?
previewText?string
Lengthlength <= 2000
format?TemplateFormat
Value in"html""markdown"
sendAt?string
Formatdate-time
reference?string
Lengthlength <= 255
tracking?
listId?string
Match^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$

Response Body

OK

application/json
  1. response
id*string
Formatuuid
accountId*string
Formatuuid
status*OutboxStatus
Value in"queued""sending""sent""failed""cancelled"
reference*string|null
sendAt*string
Formatdate-time
attempt*integer
lastError*string|null
providerMessageId*|null
internetMessageId*string|null
sentAt*|null
createdAt*string
Formatdate-time
trackOpens*boolean
trackClicks*boolean
trackHoneypot*boolean
humanOpenCount*integer
machineOpenCount*integer
humanClickCount*integer
machineClickCount*integer
firstOpenedAt*|null
lastOpenedAt*|null
undoUntil*|null
Example Request
curl -X POST 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/outbox' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "accountId": "6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",  "status": "queued",  "reference": "string",  "sendAt": "2026-05-04T09:30:00.000Z",  "attempt": 0,  "lastError": "string",  "providerMessageId": "string",  "internetMessageId": "<CAF3x9a@mail.example.org>",  "sentAt": "2026-05-04T09:30:00.000Z",  "createdAt": "2026-05-04T09:30:00.000Z",  "trackOpens": true,  "trackClicks": true,  "trackHoneypot": true,  "humanOpenCount": 0,  "machineOpenCount": 0,  "humanClickCount": 0,  "machineClickCount": 0,  "firstOpenedAt": "2026-05-04T09:30:00.000Z",  "lastOpenedAt": "2026-05-04T09:30:00.000Z",  "undoUntil": "2026-05-04T09:30:00.000Z"}

Get a send

GET
/v1/accounts/{accountId}/outbox/{outboxId}

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

One entry with its tracking counts, for as long as the outbox keeps it: a sent message's outboxId names it.

Required scope: read.

Path Parameters

accountId*string
Formatuuid
outboxId*string
Formatuuid

Response Body

OK

application/json
  1. response
id*string
Formatuuid
accountId*string
Formatuuid
status*OutboxStatus
Value in"queued""sending""sent""failed""cancelled"
reference*string|null
sendAt*string
Formatdate-time
attempt*integer
lastError*string|null
providerMessageId*|null
internetMessageId*string|null
sentAt*|null
createdAt*string
Formatdate-time
trackOpens*boolean
trackClicks*boolean
trackHoneypot*boolean
humanOpenCount*integer
machineOpenCount*integer
humanClickCount*integer
machineClickCount*integer
firstOpenedAt*|null
lastOpenedAt*|null
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/outbox/6305f547-a117-46c0-811c-4395f19f91db'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "accountId": "6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",  "status": "queued",  "reference": "string",  "sendAt": "2026-05-04T09:30:00.000Z",  "attempt": 0,  "lastError": "string",  "providerMessageId": "string",  "internetMessageId": "<CAF3x9a@mail.example.org>",  "sentAt": "2026-05-04T09:30:00.000Z",  "createdAt": "2026-05-04T09:30:00.000Z",  "trackOpens": true,  "trackClicks": true,  "trackHoneypot": true,  "humanOpenCount": 0,  "machineOpenCount": 0,  "humanClickCount": 0,  "machineClickCount": 0,  "firstOpenedAt": "2026-05-04T09:30:00.000Z",  "lastOpenedAt": "2026-05-04T09:30:00.000Z"}

Reschedule a queued send

PATCH
/v1/accounts/{accountId}/outbox/{outboxId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Moves sendAt, earlier or later; a past sendAt sends as soon as possible. Announced as send.rescheduled.

Required scope: send.

Path Parameters

accountId*string
Formatuuid
outboxId*string
Formatuuid

Request Body

application/json
  1. body
sendAt*string
Formatdate-time

Response Body

OK

application/json
  1. response
id*string
Formatuuid
accountId*string
Formatuuid
status*OutboxStatus
Value in"queued""sending""sent""failed""cancelled"
reference*string|null
sendAt*string
Formatdate-time
attempt*integer
lastError*string|null
providerMessageId*|null
internetMessageId*string|null
sentAt*|null
createdAt*string
Formatdate-time
trackOpens*boolean
trackClicks*boolean
trackHoneypot*boolean
humanOpenCount*integer
machineOpenCount*integer
humanClickCount*integer
machineClickCount*integer
firstOpenedAt*|null
lastOpenedAt*|null
Example Request
curl -X PATCH 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/outbox/6305f547-a117-46c0-811c-4395f19f91db' \  -H 'Content-Type: application/json' \  -d '{  "sendAt": "2026-05-04T09:30:00.000Z"}'
Example Response
{  "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",  "accountId": "6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",  "status": "queued",  "reference": "string",  "sendAt": "2026-05-04T09:30:00.000Z",  "attempt": 0,  "lastError": "string",  "providerMessageId": "string",  "internetMessageId": "<CAF3x9a@mail.example.org>",  "sentAt": "2026-05-04T09:30:00.000Z",  "createdAt": "2026-05-04T09:30:00.000Z",  "trackOpens": true,  "trackClicks": true,  "trackHoneypot": true,  "humanOpenCount": 0,  "machineOpenCount": 0,  "humanClickCount": 0,  "machineClickCount": 0,  "firstOpenedAt": "2026-05-04T09:30:00.000Z",  "lastOpenedAt": "2026-05-04T09:30:00.000Z"}

Cancel a queued send

DELETE
/v1/accounts/{accountId}/outbox/{outboxId}

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Undoes a send while it waits for its sendAt.

Required scope: send.

Path Parameters

accountId*string
Formatuuid
outboxId*string
Formatuuid

Response Body

OK

application/json
  1. response
Example Request
curl -X DELETE 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/outbox/6305f547-a117-46c0-811c-4395f19f91db'
Example Response
null
GET
/v1/accounts/{accountId}/outbox/{outboxId}/tracking

Runs in your browser against api.workwae.com with the key you enter. Use a read-only key, limited to a test account if you can.

Newest first. At most 1000 are kept per send (the counts on the entry keep going), each for the tracking retention period. Each event carries what its request showed: the method (GET or HEAD), the Referer and Origin hosts, the mail client (Gmail's image proxy, web or app; Outlook on the web, desktop or mobile; Apple Mail and its Mail Privacy Protection; Yahoo, AOL, Thunderbird, Proton, Fastmail, Zoho, iCloud, a self-hosted webmail; else unknown), the device, OS and browser families, Cloudflare's visitor location (country, region, city, timezone, latitude and longitude, behind a trusted proxy only), the seconds after the send and since the send's previous hit, its sequence number, whether it was conditional, a Range request or a prefetch, the sec-fetch tuple, and the request headers (lower-cased, values cut at 1024 characters, at most 64, never Cookie, Authorization or Proxy-Authorization, forwarding and cf-* headers only from the trusted proxy; the full Referer is only here). Full addresses and headers stay with the tenant for the retention period.

Required scope: read.

Path Parameters

accountId*string
Formatuuid
outboxId*string
Formatuuid

Query Parameters

limit?integer
Range1 <= value <= 200
Default50
cursor?string
Length1 <= length <= 512

Response Body

OK

application/json
  1. response
items*array<>
nextCursor*|null
Example Request
curl -X GET 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/outbox/6305f547-a117-46c0-811c-4395f19f91db/tracking'
Example Response
{  "items": [    {      "id": "c4e6a8b0-1d3f-4a5c-8e7b-9d1f3a5c7e9b",      "kind": "open",      "linkIndex": 0,      "url": "string",      "occurredAt": "2026-05-04T09:30:00.000Z",      "userAgent": "string",      "ip": "string",      "actor": "human",      "reasons": [        "string"      ],      "isMachine": true,      "method": "string",      "refererHost": "string",      "originHost": "string",      "client": "gmail_proxy",      "device": "desktop",      "os": "windows",      "browser": "chrome",      "country": "string",      "region": "string",      "city": "string",      "timezone": "string",      "latitude": 0,      "longitude": 0,      "secondsAfterSend": 0,      "secondsSincePrevious": 0,      "sequence": 0,      "isConditional": true,      "isRange": true,      "isPrefetch": true,      "secFetch": {        "dest": "string",        "mode": "string",        "site": "string",        "user": true      },      "headers": {        "property1": "string",        "property2": "string"      }    }  ],  "nextCursor": "eyJ0eG5pZCI6IjQyIiwiaWQiOjEwMDF9"}
POST
/v1/accounts/{accountId}/mail-merges

Runs against the live API with the key you enter: this change really happens. Use a key limited to a test account.

Each of at most 100 entries becomes its own outbox entry and message, addressed to its to alone, with its own Message-ID, events, bounce matching and tracking; the answer has one result per entry, in order. Content is rendered per entry with its params (a template always; inline content only for entries that carry params, or with listId). An entry that cannot be queued fails alone, its result naming the reason (invalid_recipient, invalid_message_id, duplicate_message_id, template_render_failed, plan_limit_sends once the day's sends run out, suppressed when listId is given and the address is on that list or the global list, internal_error), while the others go ahead; the call itself succeeds. The body is the send body without to, cc, bcc and render: an unknown field is refused. Delivery is at-least-once, except outcome_unknown (below), and without order across retries: an account's entries are first tried in queue order, but one backing off after a failed attempt does not hold up the later ones. A retry first looks for a copy that already went out. When no SMTP reply comes after the final dot was handed to the operating system (the connection dropped or timed out, or the attempt ran out of time), the server may have accepted the message, so it is not sent again: send.failed carries code outcome_unknown and the client app decides whether to resend. A message without a sendAt later than the account's undo window (undoSendSeconds) from now waits out the window; undoUntil says until when cancelling the entry (DELETE .../outbox/{outboxId}) undoes the send. With tracking, the HTML part gets a 1x1 pixel (opens) and its http(s) links point at Workwae's redirect (clicks), reported as message.opened and message.clicked and counted on the outbox entry; text parts are left as they are. With clicks, tracking.honeypot (else the account's trackingHoneypot) also adds one hidden link only machines follow; it does nothing without clicks, so asking for it with clicks off is refused rather than ignored. Tracking is per message, not per recipient: every recipient gets the same MIME. With listId, content is always rendered, {{rcpt.unsubscribeUrl}}, {{rcpt.address}} and {{rcpt.name}} are the recipient's, and the message carries List-Unsubscribe with that same URL and List-Unsubscribe-Post: List-Unsubscribe=One-Click (RFC 8058). Click tracking never rewrites a List-Unsubscribe URL, so the body's link stays as written. A recipient on that list or on the global list is not sent to, at queue time and again at delivery (send.failed with code suppressed). Without listId no list is consulted. Whether the provider's DKIM signature covers the List-Unsubscribe headers is up to its signing. What holds for the whole call is checked once and refuses it. Content is rendered when the request is taken, so later template edits never change it. Answers 200: the merge itself is not kept, its entries are in the outbox.

Required scope: send.

Path Parameters

accountId*string
Formatuuid

Request Body

application/json
  1. body
replyTo?array<>
Itemsitems <= 10
Default[]
subject?string
Lengthlength <= 998
text?string
Lengthlength <= 2097152
html?string
Lengthlength <= 2097152
attachments?array<>
Itemsitems <= 50
Default[]
inReplyToMessageId?string
Formatuuid
from?
signature?boolean
priority?MessagePriority
Default"normal"
Value in"high""normal""low"
template?string
Formatuuid
previewText?string
Lengthlength <= 2000
format?TemplateFormat
Value in"html""markdown"
sendAt?string
Formatdate-time
reference?string
Lengthlength <= 255
tracking?
listId?string
Match^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$
entries*array<>
Items1 <= items <= 100

Response Body

OK

application/json
  1. response
items*array<|>
Example Request
curl -X POST 'https://api.workwae.com/v1/accounts/6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b/mail-merges' \  -H 'Content-Type: application/json' \  -d '{  "entries": [    {      "to": {        "address": "ada@example.com"      }    }  ]}'
Example Response
{  "items": [    {      "success": true,      "to": {        "name": "Ada Lovelace",        "address": "ada@example.com"      },      "outboxId": "6305f547-a117-46c0-811c-4395f19f91db",      "messageId": "string",      "sendAt": "2026-05-04T09:30:00.000Z",      "undoUntil": "2026-05-04T09:30:00.000Z"    }  ]}