Purple Mail is the suite's email system, the category that tools like Microsoft Outlook and Gmail sit in. Every person on the shared roster has a mailbox with Inbox, Sent, Drafts, Archive, Junk and Trash plus custom folders, inbox rules, a signature and automatic replies. Mail is stored per mailbox: sending writes a sent copy for the sender and an inbox copy for each To, Cc and Bcc recipient, linked by conversation_id (the thread) and internet_message_id (the RFC 5322 Message-ID). Start with the current user's own mail: list-my-conversations for threads, list-my-messages for individual emails with OData filters and _search, get-my-conversation for a whole thread.
---
**Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.
Purple Mail is the suite's email system, the category that tools like Microsoft Outlook and Gmail sit in. Every person on the shared roster has a mailbox with Inbox, Sent, Drafts, Archive, Junk and Trash plus custom folders, inbox rules, a signature and automatic replies. Mail is stored per mailbox: sending writes a sent copy for the sender and an inbox copy for each To, Cc and Bcc recipient, linked by conversation_id (the thread) and internet_message_id (the RFC 5322 Message-ID). Start with the current user's own mail: list-my-conversations for threads, list-my-messages for individual emails with OData filters and _search, get-my-conversation for a whole thread.
---
**Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.
Operations
POST /messages/send
Send an email
Composes and delivers a NEW email: the sender gets a copy in sent, each To, Cc and Bcc recipient gets a copy in inbox (Bcc stays hidden from recipients). Recipients' inbox rules run; recipients with automatic replies on answer once. Attachments are metadata only. To answer an existing email use reply so the thread stays linked; to pass one on use forward.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__send__message",
"summary": "Send an email",
"description": "Composes and delivers a NEW email: the sender gets a copy in sent, each To, Cc and Bcc recipient gets a copy in inbox (Bcc stays hidden from recipients). Recipients' inbox rules run; recipients with automatic replies on answer once. Attachments are metadata only. To answer an existing email use reply so the thread stays linked; to pass one on use forward.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SendMailRequest"
}
}
}
},
"responses": {
"201": {
"description": "The sender's copy of the delivered email",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
},
"400": {
"description": "Missing sender, recipients or subject, or an invalid address"
}
}
}
POST /messages/{id}/reply
Reply to an email
Replies from the mailbox that holds the message (its owner_email) to the sender (or its Reply-To), or to everyone on it with reply_all. The subject gets a Re: prefix and the reply joins the same conversation_id with in_reply_to_message_id and references set. Pass the id of a copy in the current user's mailbox (from list-my-messages or get-my-conversation).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__reply__message",
"summary": "Reply to an email",
"description": "Replies from the mailbox that holds the message (its owner_email) to the sender (or its Reply-To), or to everyone on it with reply_all. The subject gets a Re: prefix and the reply joins the same conversation_id with in_reply_to_message_id and references set. Pass the id of a copy in the current user's mailbox (from list-my-messages or get-my-conversation).",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string",
"example": "MSG-00001"
},
"description": "Message ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReplyMailRequest"
}
}
}
},
"responses": {
"201": {
"description": "The sender's copy of the reply",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
},
"400": {
"description": "body_content missing, or no one to reply to"
},
"404": {
"description": "Message not found"
},
"409": {
"description": "The message is a draft"
}
}
}
POST /messages/{id}/forward
Forward an email
Forwards a message from the mailbox that holds it to new recipients, with an optional comment above the forwarded text. Attachments are carried along. Starts a new conversation.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__forward__message",
"summary": "Forward an email",
"description": "Forwards a message from the mailbox that holds it to new recipients, with an optional comment above the forwarded text. Attachments are carried along. Starts a new conversation.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string",
"example": "MSG-00001"
},
"description": "Message ID"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ForwardMailRequest"
}
}
}
},
"responses": {
"201": {
"description": "The sender's copy of the forwarded email",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
},
"400": {
"description": "Missing or invalid recipients"
},
"404": {
"description": "Message not found"
}
}
}
POST /messages/{id}/send
Send a draft
Delivers a message from the drafts folder to its To, Cc and Bcc recipients and removes the draft. Create a draft first with the store-message tool (folder drafts) or edit one with update.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__send__draft",
"summary": "Send a draft",
"description": "Delivers a message from the drafts folder to its To, Cc and Bcc recipients and removes the draft. Create a draft first with the store-message tool (folder drafts) or edit one with update.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string",
"example": "MSG-00001"
},
"description": "Message ID"
}
],
"responses": {
"201": {
"description": "The sender's copy of the delivered email",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
},
"400": {
"description": "The draft has no recipients or subject"
},
"404": {
"description": "Draft not found"
},
"409": {
"description": "The message is not a draft, or was already sent"
}
}
}
GET /me/messages
List my email
Individual emails in the current user's mailbox (the X-PS-Impersonate-User header, or the instance's default MCP user), every folder unless you filter. Common calls: folder=inbox for the inbox; _filter "isRead eq false" for unread; _filter "isFlagged eq true"; _filter "importance eq 'high'"; _filter "receivedDateTime ge 2026-11-01T00:00:00Z"; _filter "contains(from_email,'vendor')"; _search "budget" (subject, body, sender, recipients, categories); _orderby "receivedDateTime desc"; _top 10. For threads use list-my-conversations instead. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__list__my_messages",
"summary": "List my email",
"description": "Individual emails in the current user's mailbox (the X-PS-Impersonate-User header, or the instance's default MCP user), every folder unless you filter. Common calls: folder=inbox for the inbox; _filter \"isRead eq false\" for unread; _filter \"isFlagged eq true\"; _filter \"importance eq 'high'\"; _filter \"receivedDateTime ge 2026-11-01T00:00:00Z\"; _filter \"contains(from_email,'vendor')\"; _search \"budget\" (subject, body, sender, recipients, categories); _orderby \"receivedDateTime desc\"; _top 10. For threads use list-my-conversations instead. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "folder",
"schema": {
"type": "string",
"example": "inbox",
"description": "One of inbox, sent, drafts, archive, junk, trash, or a custom folder id (fld_…) from the folders tool."
},
"description": "Only this folder"
},
{
"in": "query",
"name": "conversation_id",
"schema": {
"type": "string"
},
"description": "Only one conversation"
},
{
"in": "query",
"name": "importance",
"schema": {
"type": "string",
"enum": [
"low",
"normal",
"high"
]
},
"description": "Only this importance"
},
{
"in": "query",
"name": "isRead",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"description": "Only read (true) or unread (false) mail"
},
{
"in": "query",
"name": "isFlagged",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"description": "Only flagged mail"
},
{
"in": "query",
"name": "has_attachments",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"description": "Only mail with attachments"
},
{
"in": "query",
"name": "inference_classification",
"schema": {
"type": "string",
"enum": [
"focused",
"other"
]
},
"description": "Focused Inbox tab"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"example": 50
},
"description": "Page size (legacy)"
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Pagination cursor (legacy)"
},
{
"in": "query",
"name": "search",
"schema": {
"type": "string"
},
"description": "Free-text search (legacy; prefer _search)"
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MailMessage"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "No current user, or an invalid OData query"
}
}
}
GET /me/conversations
List my conversations (threads)
The current user's mailbox grouped into threads, newest activity first, each with subject, participants, message_count, unread_count, latest_preview and the folders its copies are in. This is what a mail client's list shows; use it to answer "what's in my inbox" and then get-my-conversation for the whole thread. folder=inbox limits to threads with a copy in the inbox; _filter "unread_count gt 0" for unread threads; _search matches subject, participants and the latest preview. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__list__my_conversations",
"summary": "List my conversations (threads)",
"description": "The current user's mailbox grouped into threads, newest activity first, each with subject, participants, message_count, unread_count, latest_preview and the folders its copies are in. This is what a mail client's list shows; use it to answer \"what's in my inbox\" and then get-my-conversation for the whole thread. folder=inbox limits to threads with a copy in the inbox; _filter \"unread_count gt 0\" for unread threads; _search matches subject, participants and the latest preview. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "folder",
"schema": {
"type": "string",
"example": "inbox",
"description": "One of inbox, sent, drafts, archive, junk, trash, or a custom folder id (fld_…) from the folders tool."
},
"description": "Only threads with a copy in this folder"
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MailConversation"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "No current user, or an invalid OData query"
}
}
}
GET /me/conversations/{conversationId}
Get a conversation (whole thread)
Every copy of one thread in the current user's mailbox, oldest first, with full headers and bodies. Use the latest message's id with reply.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__get__my_conversation",
"summary": "Get a conversation (whole thread)",
"description": "Every copy of one thread in the current user's mailbox, oldest first, with full headers and bodies. Use the latest message's id with reply.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "conversationId",
"required": true,
"schema": {
"type": "string",
"example": "CONV-0001"
},
"description": "Conversation ID (conversation_id on any message in the thread)"
}
],
"responses": {
"200": {
"description": "The thread",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailConversationDetail"
}
}
}
},
"400": {
"description": "No current user"
},
"404": {
"description": "No such conversation in this mailbox"
}
}
}
PATCH /me/conversations/{conversationId}
Archive, move, flag or mark a whole thread read
Applies isRead, isFlagged and/or folder to every copy of the thread in the current user's mailbox in one call (drafts and sent copies keep their folder). Use folder archive to archive a thread, trash to delete it, inbox to restore it.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__update__my_conversation",
"summary": "Archive, move, flag or mark a whole thread read",
"description": "Applies isRead, isFlagged and/or folder to every copy of the thread in the current user's mailbox in one call (drafts and sent copies keep their folder). Use folder archive to archive a thread, trash to delete it, inbox to restore it.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "conversationId",
"required": true,
"schema": {
"type": "string",
"example": "CONV-0001"
},
"description": "Conversation ID (conversation_id on any message in the thread)"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConversationPatch"
}
}
}
},
"responses": {
"200": {
"description": "How many copies changed, and the thread after the change",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"conversation_id": {
"type": "string"
},
"updated": {
"type": "integer"
},
"messages": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
}
}
}
},
"400": {
"description": "Invalid change, or no current user"
},
"404": {
"description": "No such conversation in this mailbox"
}
}
}
GET /me/folders
List my folders with counts
The current user's folders: inbox, sent, drafts, archive, junk, trash and their custom folders, each with total_item_count and unread_item_count. Custom folder ids (fld_…) are what you pass as folder when moving mail or writing a rule.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__list__my_folders",
"x-odata": {
"enabled": false,
"reason": "A fixed set of six well-known folders plus the mailbox's custom folders, with live counts; not a queryable collection."
},
"summary": "List my folders with counts",
"description": "The current user's folders: inbox, sent, drafts, archive, junk, trash and their custom folders, each with total_item_count and unread_item_count. Custom folder ids (fld_…) are what you pass as folder when moving mail or writing a rule.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"responses": {
"200": {
"description": "Folders with counts",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MailFolderCount"
}
},
"total": {
"type": "integer"
}
}
}
}
}
},
"400": {
"description": "No current user"
}
}
}
POST /me/folders
Create a folder
Creates a custom folder in the current user's mailbox. Names are unique per mailbox; built-in folder names are reserved.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__create__my_folder",
"summary": "Create a folder",
"description": "Creates a custom folder in the current user's mailbox. Names are unique per mailbox; built-in folder names are reserved.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateFolderRequest"
}
}
}
},
"responses": {
"201": {
"description": "The new folder",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailFolder"
}
}
}
},
"400": {
"description": "Missing or invalid display_name, or no current user"
},
"409": {
"description": "A folder with that name already exists"
}
}
}
GET /me/rules
List my inbox rules
The current user's inbox rules in run order. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__list__my_rules",
"summary": "List my inbox rules",
"description": "The current user's inbox rules in run order. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "is_enabled",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"description": "Only enabled or disabled rules"
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MailRule"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "No current user, or an invalid OData query"
}
}
}
POST /me/rules
Create an inbox rule
Adds a rule to the current user's mailbox. It runs on every new email that lands in their inbox from now on; call apply-rule to run it over mail already there. conditions are matched case-insensitively (all given conditions must hold); actions need at least one of move_to_folder, mark_as_read, flag, assign_categories.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__create__my_rule",
"summary": "Create an inbox rule",
"description": "Adds a rule to the current user's mailbox. It runs on every new email that lands in their inbox from now on; call apply-rule to run it over mail already there. conditions are matched case-insensitively (all given conditions must hold); actions need at least one of move_to_folder, mark_as_read, flag, assign_categories.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateRuleRequest"
}
}
}
},
"responses": {
"201": {
"description": "The new rule",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailRule"
}
}
}
},
"400": {
"description": "Invalid conditions or actions, or no current user"
}
}
}
POST /rules/{id}/apply
Run a rule over the existing inbox
Runs one rule over every message currently in its owner's inbox and applies its actions to the matches. Returns how many matched and how many changed.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__apply__rule",
"summary": "Run a rule over the existing inbox",
"description": "Runs one rule over every message currently in its owner's inbox and applies its actions to the matches. Returns how many matched and how many changed.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string",
"example": "RULE-0001"
},
"description": "Rule ID"
}
],
"responses": {
"200": {
"description": "Counts of matched and changed messages",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"rule_id": {
"type": "string"
},
"matched": {
"type": "integer"
},
"updated": {
"type": "integer"
},
"message_ids": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
},
"404": {
"description": "Rule not found"
}
}
}
POST /admin/reseed
Reset mail to the seed (admin)
Replaces every mailbox (or one, with mailbox) with the default seed content, or with the content posted in the same format as seed-content.json: messages, settings, folders and rules. Instance owners only; not exposed as an MCP tool.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__reseed__mailboxes",
"x-mcp": false,
"summary": "Reset mail to the seed (admin)",
"description": "Replaces every mailbox (or one, with mailbox) with the default seed content, or with the content posted in the same format as seed-content.json: messages, settings, folders and rules. Instance owners only; not exposed as an MCP tool.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"mailbox": {
"type": "string",
"format": "email",
"description": "Reset only this mailbox"
},
"content": {
"type": "object",
"description": "Mail content (see seed-content.json); default seed when omitted"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Counts of what was written"
},
"400": {
"description": "Invalid mailbox or content"
}
}
}
POST /admin/import
Import a thread (admin)
Adds conversations to the mailboxes of everyone on them (or only to mailbox) without touching existing mail. format json takes the seed-content.json shape; format eml takes one or more RFC 822 messages, grouped into threads by subject. Instance owners only; not exposed as an MCP tool.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__import__messages",
"x-mcp": false,
"summary": "Import a thread (admin)",
"description": "Adds conversations to the mailboxes of everyone on them (or only to mailbox) without touching existing mail. format json takes the seed-content.json shape; format eml takes one or more RFC 822 messages, grouped into threads by subject. Instance owners only; not exposed as an MCP tool.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"content"
],
"properties": {
"format": {
"type": "string",
"enum": [
"json",
"eml"
],
"example": "json"
},
"content": {
"description": "Mail content object (json) or .eml text / list of .eml texts (eml)"
},
"mailbox": {
"type": "string",
"format": "email",
"description": "Store copies only in this mailbox"
}
}
}
}
}
},
"responses": {
"201": {
"description": "How many mailbox copies were written, and their conversation ids"
},
"400": {
"description": "Invalid content"
}
}
}
GET /me/settings
Get my mailbox settings
The current user's display name, signature, time zone and automatic reply (out of office) settings. owner_email here is also the address to use as a filter value elsewhere.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__get__my_settings",
"summary": "Get my mailbox settings",
"description": "The current user's display name, signature, time zone and automatic reply (out of office) settings. owner_email here is also the address to use as a filter value elsewhere.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"responses": {
"200": {
"description": "Mailbox settings",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailboxSettings"
}
}
}
},
"400": {
"description": "No current user"
},
"404": {
"description": "The current user has no mailbox"
}
}
}
PATCH /me/settings
Update my mailbox settings
Turn automatic replies (out of office) on or off, set their message and window (auto_reply_start / auto_reply_end, ISO date-times), or change the signature, display name or time zone.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__update__my_settings",
"summary": "Update my mailbox settings",
"description": "Turn automatic replies (out of office) on or off, set their message and window (auto_reply_start / auto_reply_end, ISO date-times), or change the signature, display name or time zone.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailboxSettings"
}
}
}
},
"responses": {
"200": {
"description": "Updated settings",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailboxSettings"
}
}
}
},
"400": {
"description": "Invalid update, or no current user"
},
"404": {
"description": "The current user has no mailbox"
}
}
}
GET /messages
List email across mailboxes
Email in every Purple Mail mailbox. Each copy has owner_email (whose mailbox) and folder; pass owner_email to read one person's mail. For the current user's own mail prefer list-my-messages or list-my-conversations. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List email across mailboxes",
"description": "Email in every Purple Mail mailbox. Each copy has owner_email (whose mailbox) and folder; pass owner_email to read one person's mail. For the current user's own mail prefer list-my-messages or list-my-conversations. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
},
{
"in": "query",
"name": "owner_email",
"schema": {
"type": "string",
"format": "email"
},
"description": "Only this mailbox"
},
{
"in": "query",
"name": "folder",
"schema": {
"type": "string",
"example": "inbox",
"description": "One of inbox, sent, drafts, archive, junk, trash, or a custom folder id (fld_…) from the folders tool."
},
"description": "Only this folder"
},
{
"in": "query",
"name": "conversation_id",
"schema": {
"type": "string"
},
"description": "Only one conversation"
},
{
"in": "query",
"name": "from_email",
"schema": {
"type": "string",
"format": "email"
},
"description": "Only mail from this sender"
},
{
"in": "query",
"name": "importance",
"schema": {
"type": "string",
"enum": [
"low",
"normal",
"high"
]
},
"description": "Only this importance"
},
{
"in": "query",
"name": "isRead",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"description": "Only read (true) or unread (false) mail"
},
{
"in": "query",
"name": "isFlagged",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"description": "Only flagged mail"
},
{
"in": "query",
"name": "has_attachments",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"description": "Only mail with attachments"
},
{
"in": "query",
"name": "inference_classification",
"schema": {
"type": "string",
"enum": [
"focused",
"other"
]
},
"description": "Focused Inbox tab"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MailMessage"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
}
}
}
POST /messages
Store a message in a mailbox
Writes a message straight into one mailbox without delivering it: a draft (folder drafts, then send-draft) or, for demo set-up, a received email. Requires owner_email and folder; everything else defaults. To actually send an email use send.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Store a message in a mailbox",
"description": "Writes a message straight into one mailbox without delivering it: a draft (folder drafts, then send-draft) or, for demo set-up, a received email. Requires owner_email and folder; everything else defaults. To actually send an email use send.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
},
"responses": {
"201": {
"description": "Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
}
}
}
GET /messages/{id}
Get an email
One mailbox copy by id, with full headers, body and attachment metadata.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get an email",
"description": "One mailbox copy by id, with full headers, body and attachment metadata.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /messages/{id}
Update an email
Mark read or unread (isRead), flag (isFlagged), move between folders (folder: inbox, archive, junk, trash or a custom fld_… id), set categories or importance, or edit a draft's recipients, subject, body and attachments.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Update an email",
"description": "Mark read or unread (isRead), flag (isFlagged), move between folders (folder: inbox, archive, junk, trash or a custom fld_… id), set categories or importance, or edit a draft's recipients, subject, body and attachments.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "Fields to update (partial)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailMessage"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
DELETE /messages/{id}
Permanently delete an email
Removes one mailbox's copy for good. To delete the way a mail client does, move it to trash with update instead.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Permanently delete an email",
"description": "Removes one mailbox's copy for good. To delete the way a mail client does, move it to trash with update instead.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Deleted",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
}
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /settings
List mailbox settings
Display name, signature, time zone and out-of-office settings for every mailbox, i.e. the directory of who has a mailbox. Filter auto_reply_enabled eq true to see who is out of office. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List mailbox settings",
"description": "Display name, signature, time zone and out-of-office settings for every mailbox, i.e. the directory of who has a mailbox. Filter auto_reply_enabled eq true to see who is out of office. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
},
{
"in": "query",
"name": "owner_email",
"schema": {
"type": "string",
"format": "email"
},
"description": "Only this mailbox"
},
{
"in": "query",
"name": "auto_reply_enabled",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"description": "Only mailboxes with automatic replies on or off"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MailboxSettings"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
}
}
}
GET /settings/{id}
Get a mailbox's settings
By owner email.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a mailbox's settings",
"description": "By owner email.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailboxSettings"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
GET /folders
List custom folders across mailboxes
Custom folders in every mailbox (well-known folders aren't stored). For the current user's folders with counts use list-my-folders. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List custom folders across mailboxes",
"description": "Custom folders in every mailbox (well-known folders aren't stored). For the current user's folders with counts use list-my-folders. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
},
{
"in": "query",
"name": "owner_email",
"schema": {
"type": "string",
"format": "email"
},
"description": "Only this mailbox's folders"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MailFolder"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
}
}
}
GET /folders/{id}
Get a folder
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a folder",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailFolder"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /folders/{id}
Rename a folder
Only display_name can change.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Rename a folder",
"description": "Only display_name can change.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "Fields to update (partial)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailFolder"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailFolder"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
DELETE /folders/{id}
Delete a folder
Deletes a custom folder and moves the mail in it back to the inbox.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"operationId": "mail__delete__folder",
"summary": "Delete a folder",
"description": "Deletes a custom folder and moves the mail in it back to the inbox.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string",
"example": "fld_3f2a9c1b0d4e"
},
"description": "Folder ID"
}
],
"responses": {
"200": {
"description": "Deleted; how many messages moved to the inbox",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
},
"moved": {
"type": "integer"
}
}
}
}
}
},
"404": {
"description": "Folder not found"
}
}
}
GET /rules
List inbox rules across mailboxes
Inbox rules in every mailbox. For the current user's rules use list-my-rules; create with create-my-rule. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "List inbox rules across mailboxes",
"description": "Inbox rules in every mailbox. For the current user's rules use list-my-rules; create with create-my-rule. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "query",
"name": "limit",
"schema": {
"type": "integer",
"default": 50
},
"description": "Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."
},
{
"in": "query",
"name": "cursor",
"schema": {
"type": "string"
},
"description": "Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."
},
{
"in": "query",
"name": "$filter",
"schema": {
"type": "string"
},
"description": "OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."
},
{
"in": "query",
"name": "$select",
"schema": {
"type": "string"
},
"description": "Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."
},
{
"in": "query",
"name": "$orderby",
"schema": {
"type": "string"
},
"description": "Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."
},
{
"in": "query",
"name": "$search",
"schema": {
"type": "string"
},
"description": "Free-text, case-insensitive substring match across the resource's searchable fields."
},
{
"in": "query",
"name": "$top",
"schema": {
"type": "integer"
},
"description": "Maximum number of items to return (page size)."
},
{
"in": "query",
"name": "$skip",
"schema": {
"type": "integer"
},
"description": "Number of matching items to skip before returning results (offset paging). Combine with $top."
},
{
"in": "query",
"name": "$count",
"schema": {
"type": "boolean"
},
"description": "When true, include @odata.count (total matches after $filter/$search, before paging) in the response."
},
{
"in": "query",
"name": "owner_email",
"schema": {
"type": "string",
"format": "email"
},
"description": "Only this mailbox's rules"
},
{
"in": "query",
"name": "is_enabled",
"schema": {
"type": "string",
"enum": [
"true",
"false"
]
},
"description": "Only enabled or disabled rules"
}
],
"responses": {
"200": {
"description": "Paginated list",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MailRule"
}
},
"nextCursor": {
"type": "string",
"nullable": true,
"description": "Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."
},
"total": {
"type": "integer",
"description": "Total documents in the collection."
},
"@odata.count": {
"type": "integer",
"description": "Present only when $count=true: total matches after $filter/$search."
},
"@odata.truncated": {
"type": "boolean",
"description": "Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."
}
}
}
}
}
},
"400": {
"description": "Invalid OData query (e.g. malformed $filter)"
}
}
}
GET /rules/{id}
Get a rule
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Get a rule",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailRule"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
PATCH /rules/{id}
Update a rule
Change display_name, sequence, is_enabled, conditions or actions. Set is_enabled false to pause a rule.
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Update a rule",
"description": "Change display_name, sequence, is_enabled, conditions or actions. Set is_enabled false to pause a rule.",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"description": "Fields to update (partial)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailRule"
}
}
}
},
"responses": {
"200": {
"description": "Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MailRule"
}
}
}
},
"404": {
"description": "Not found"
}
}
}
DELETE /rules/{id}
Delete a rule
Authentication
[
{
"patAuth": []
}
]
Request, responses and operation details
{
"summary": "Delete a rule",
"parameters": [
{
"$ref": "#/components/parameters/InstanceId"
},
{
"in": "path",
"name": "id",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Deleted",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
}
}
}
}
},
"404": {
"description": "Not found"
}
}
}