Email, calendar & files

Email

List, search and read threads, send and reply from a connected Gmail or Outlook mailbox, manage folders and attachments, and stay inside the daily send cap.

Send and read email from your users' own Gmail or Outlook mailboxes. Mail goes out from their address, lands in their Sent folder and threads normally for the recipient. Every call takes the accountId of a Google or Microsoft account connected with the email area (see Email, calendar & files).

#Send an email

curl -X POST https://api.pozy.ai/v1/email/messages \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "6710c501e4b0d2f1a8c95e77",
    "to": [{ "email": "jordan@client.example", "name": "Jordan Lee" }],
    "subject": "Your proposal for November",
    "html": "<p>Hi Jordan, the proposal is attached. Happy to walk you through it on Thursday.</p>",
    "attachments": [
      { "filename": "proposal.pdf", "contentType": "application/pdf", "url": "https://files.example.com/proposal-nov.pdf" }
    ],
    "metadata": { "dealId": "deal_4471" }
  }'

The call returns 201 with the sent copy, including its id and threadId.

Field Rules
to, cc, bcc Lists of { email, name? }, 100 at most in each. to cannot be empty
replyTo 5 addresses at most
fromName The name recipients see. The sending address cannot change: it is the mailbox itself
subject Required; 998 characters at most
text, html Send one or both
attachments 20 at most. Each is { filename, contentType } plus either url or base64. Give one a contentId to show it inline from html. Pozy must be able to fetch a url in 20 seconds
replyToMessageId Puts the email in the same thread as that message
headers Extra headers; every name must begin with X-
metadata Your own string keys and values, returned on the message

The schema also lists scheduledFor, holdAsDraft, trackOpens and trackLinks. None of them works yet, and sending one gets 400.

#Reply

A reply stays in the original thread; Pozy adds the headers that keep it there. By default it goes back to the sender, and mode: "replyAll" includes everyone on the original. to and subject are optional and fall back to the original message's.

curl -X POST https://api.pozy.ai/v1/email/messages/18f2c0a9d3b1e7f4/reply \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "accountId": "6710c501e4b0d2f1a8c95e77", "mode": "replyAll", "text": "Thursday at 10:00 works. Talk then." }'

Outlook quotes the original below your reply. Replies count against the daily send cap like any send.

GET /v1/email/threads returns threads, GET /v1/email/messages returns single messages. Both are newest first, read the inbox unless you pass folder, and page with cursor and limit (up to 200, default 50).

cURL
curl "https://api.pozy.ai/v1/email/threads?accountId=6710c501e4b0d2f1a8c95e77&folder=inbox&unread=true&from=jordan@client.example&limit=20" \
  -H "Authorization: Bearer $POZY_API_KEY"
Parameter What it does
folder One of inbox, sent, drafts, trash, spam and archive, or the id of a folder or label listed by GET /v1/email/folders
unread, starred, hasAttachment true or false
from, to, cc, bcc, anyEmail An email address; anyEmail matches it in any of those fields
subject Text to find in the subject line
after, before ISO 8601 dates that bound the message date
searchQueryNative A query in the mail provider's own syntax, up to 500 characters, sent through untouched. Gmail: has:attachment larger:5M. Outlook: subject:invoice
sortOrder desc, the default, or asc

Some filters a provider cannot run on its side. Pozy then filters each page after fetching it and lists those filters in meta.filteredClientSide. A page filtered that way may come back with fewer than limit items even though pagination.hasMore is still true: page on until hasMore turns false. Outlook does not mix its native search with other filters, so with searchQueryNative set, the other filters are applied by Pozy as well.

#Open threads and messages

  • GET /v1/email/threads/{threadId}?accountId=... gives you a thread and every message in it.
  • GET /v1/email/messages/{messageId}?accountId=... returns a single message, including text, html and attachments. includeHeaders=true adds the headers; raw=true returns the whole MIME message.
  • GET /v1/email/attachments/{attachmentId}?accountId=...&messageId=... downloads one attachment, sent with its filename and content type.

#Organise mail

To mark, flag or file mail, send accountId with any of unread, starred and folder to PATCH /v1/email/messages/{messageId} or PATCH /v1/email/threads/{threadId}. On Outlook, starred is the flag. A new folder moves the mail there; "folder": "archive" is how you archive. DELETE /v1/email/messages/{messageId}?accountId=... sends the message to the trash, which Outlook calls Deleted Items.

#Send caps and cooldowns

Limit Gmail Google Workspace Outlook
Sends a day per mailbox 200 500 1,000
Attachment size 25 MB in total 25 MB in total 3 MB each, 25 MB in total

The count starts again at midnight UTC. A reply is a send, and so is an attempt the provider turns down. Each send response carries three headers to help you pace:

Header Meaning
X-Pozy-Send-Limit Today's cap for this mailbox
X-Pozy-Send-Remaining Sends left today
X-Pozy-Send-Reset When the cap resets

GET /v1/accounts/{accountId}/health reports the same as sendCap.

Use these mailboxes for personal, one-to-one email, not bulk sends. If one body goes to over 50 different recipients inside an hour, the mailbox is paused for 10 minutes. Each further pause lasts twice as long as the one before, up to 24 hours. The start of a pause fires account.rate_limited:

account.rate_limited
{
  "id": "evt_2b7f0c1d9e8a4f36",
  "event": "account.rate_limited",
  "timestamp": "2026-10-30T11:20:00.000Z",
  "account": {
    "accountId": "6710c501e4b0d2f1a8c95e77",
    "profileId": "6710c3a9e4b0d2f1a8c95e21",
    "platform": "google",
    "username": "ana@example.com",
    "reason": "recipient_burst",
    "cooldownMinutes": 10,
    "resetsAt": "2026-10-30T11:30:00.000Z"
  }
}

reason is recipient_burst, provider_rejection, bounces or daily_cap. For newsletters and bulk sends, use broadcasts on a messaging channel instead.

#Email in the inbox

Mail received while the account is connected, and mail you send through the API, also appears as inbox conversations (one per thread) and fires message.received or message.sent. The subject, cc and other email fields are under metadata.email on the inbox message. Older mail is reachable through the endpoints on this page, not the inbox.

#Errors

Error Cause Fix
400 invalid_field_value A bad field: a non-X- header, attachments over the size limit, scheduledFor Fix the field named in param
401 reconnect_required The provider refused the account's access Reconnect the account
412 account_scope_missing The account has no email area Reconnect with email in scopes
412 admin_consent_required A Microsoft tenant needs admin approval See admin consent
429 email_send_cap_reached Today's cap is used. details.cap has limit, used and resetsAt Wait for resetsAt
429 rate_limited A cooldown after a burst, or provider throttling Wait for Retry-After and send fewer identical emails

#Endpoints

Endpoint What it does
GET /v1/email/threads List email threads
GET /v1/email/threads/{threadId} Get an email thread with its messages
PATCH /v1/email/threads/{threadId} Update every message in a thread
GET /v1/email/messages List email messages
POST /v1/email/messages Send an email
GET /v1/email/messages/{messageId} Get an email message
PATCH /v1/email/messages/{messageId} Mark read or starred, or move a message
DELETE /v1/email/messages/{messageId} Move a message to trash
POST /v1/email/messages/{messageId}/reply Reply to a message
GET /v1/email/folders List mailbox folders
GET /v1/email/attachments/{attachmentId} Download an attachment

#Webhook events

Event When it fires
account.rate_limited Mailbox sending cooldown started.
message.received Message received.
message.sent Message sent.