WarmblyDocs
Endpoint reference

Unified inbox

Read, search, and triage incoming mail across every mailbox, then reply, label, snooze, and manage scheduled sends.

The unified inbox (unibox) is the org-wide view of everything that lands in your connected mailboxes. These endpoints power the dashboard's inbox list, thread view, scope rail, conversation labels, snoozes, and scheduled-send queue. Every route is gated on the unified-inbox feature, so the calling organization needs an active trial or paid subscription; without it the endpoint returns 403.

The list, thread, overview, and snooze data is org-scoped, not per-user. Two members of the same organization see the same inbox, the same unread badge, and the same threads. Snoozes and conversation labels are attached to the calling user.

List incoming mail

GET /unibox

Returns the inbox list, collapsed to one row per thread (the newest message), with filtering and cursor pagination. Auth: Scope READ_UNIBOX · Org permission access_unibox.

ParameterInTypeDescription
cursorquerystringOpaque pagination cursor from a previous response.
limitqueryintegerPage size. Clamped to the server's min/max.
fromquerystringFilter by sender address (substring).
subjectquerystringFilter by subject (substring).
unseenquerybooleantrue returns only threads with unread messages.
awaiting_replyquerybooleantrue returns only threads where the latest message was sent by you (recipient has not replied).
snoozedquerystringtrue returns only snoozed threads. Omit to exclude snoozed threads (default).
sincequerystringLower bound on date, YYYY-MM-DD.
untilquerystringUpper bound on date, YYYY-MM-DD.
email_idquerystringRestrict to a single mailbox by UUID.
email_idsquerystringComma-separated mailbox UUIDs. A thread matches if it landed in any of them. Invalid UUIDs are dropped.
category_idsquerystringComma-separated conversation-label UUIDs. A thread matches if it carries any of them.

The response is a data plus pagination envelope. Each row summarises the whole thread behind it (message_count, has_unread) plus the conversation's labels.

Response

{
  "data": [
    {
      "id": "9b6f0e2a-3c4d-4f1a-8b2e-1a2b3c4d5e6f",
      "email_id": "2a1b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "thread_id": "thread-af83b21",
      "from_addr": ["Jane Doe <[email protected]>"],
      "to_addr": ["[email protected]"],
      "subject": "Re: Following up on pricing",
      "snippet": "Thanks for the details, this looks great...",
      "internal_date": "2026-06-11T14:22:09Z",
      "seen": false,
      "message_count": 4,
      "has_unread": true,
      "labels": [
        { "id": "c0ffee00-0000-4000-8000-000000000001", "title": "Interested", "color": "#16a34a" }
      ]
    }
  ],
  "pagination": {
    "next_cursor": "eyJpZCI6Ii4uLiJ9",
    "has_more": true
  }
}

Get unread count

GET /unibox/count

Returns the org-wide unread message count, optionally scoped to one mailbox. Backs the inbox badge. Auth: Scope READ_UNIBOX · Org permission access_unibox.

ParameterInTypeDescription
email_idquerystringOptional mailbox UUID to count unread for a single mailbox.

Response

{
  "count": 37
}

Get inbox overview

GET /unibox/overview

Rolls up the scope rail and top metric strip in one call: unread, today, week, snoozed, awaiting-reply, and pending-scheduled counts, plus per-mailbox, per-tag, and per-conversation-label breakdowns. Auth: Scope READ_UNIBOX · Org permission access_unibox.

Response

{
  "total": 1284,
  "unread": 37,
  "today": 12,
  "week": 88,
  "snoozed": 3,
  "awaiting_reply": 9,
  "scheduled_pending": 2,
  "scheduled_pending_max": 50,
  "mailboxes": [
    {
      "id": "2a1b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "email": "[email protected]",
      "name": "Sales",
      "unread": 21,
      "total": 640
    }
  ],
  "tags": [
    {
      "id": "1f2e3d4c-5b6a-7980-a1b2-c3d4e5f60718",
      "title": "Outbound",
      "color": "#2563eb",
      "unread": 14,
      "total": 410
    }
  ],
  "categories": [
    {
      "id": "c0ffee00-0000-4000-8000-000000000001",
      "title": "Interested",
      "color": "#16a34a",
      "unread": 5,
      "total": 62
    }
  ],
  "generated_at": "2026-06-11T14:25:00Z",
  "window_today_start": "2026-06-11T00:00:00Z",
  "window_week_start": "2026-06-05T00:00:00Z"
}

Get a thread

GET /unibox/thread

Returns every message in a single conversation, oldest-first style message rows, with cursor pagination. The mailbox filter is optional: with no email_id the thread is read across every mailbox in the organization (the natural unified view). Auth: Scope READ_UNIBOX · Org permission access_unibox.

ParameterInTypeDescription
thread_idquerystringRequired. The thread to read. Also accepted as id.
email_idquerystringOptional mailbox UUID to scope the thread to one mailbox. Also accepted as email.
cursorquerystringOpaque pagination cursor.
limitqueryintegerPage size. Out-of-range values return 400.

The response is a data plus pagination envelope. Each item is a full message (envelope plus body).

Response

{
  "data": [
    {
      "id": "9b6f0e2a-3c4d-4f1a-8b2e-1a2b3c4d5e6f",
      "email_id": "2a1b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "mailbox": 1,
      "thread_id": "thread-af83b21",
      "message_id": "<[email protected]>",
      "gmail_id": "18f0c2a9b7d4e5f6",
      "parent_id": "<[email protected]>",
      "uid": 4821,
      "mod_seq": 90210,
      "flags": ["\\Seen"],
      "bcc": [],
      "cc": [],
      "from_addr": ["Jane Doe <[email protected]>"],
      "in_reply_to": ["<[email protected]>"],
      "reply_to": [],
      "to_addr": ["[email protected]"],
      "subject": "Re: Following up on pricing",
      "size": 18422,
      "internal_date": "2026-06-11T14:22:09Z",
      "sent_date": "2026-06-11T14:22:00Z",
      "snippet": "Thanks for the details, this looks great...",
      "seen": true,
      "body_plain": "Thanks for the details...",
      "body_html": "<p>Thanks for the details...</p>",
      "updated_at": "2026-06-11T14:22:10Z",
      "created_at": "2026-06-11T14:22:10Z"
    }
  ],
  "pagination": {
    "next_cursor": null,
    "has_more": false
  }
}

Get thread labels

GET /unibox/thread/labels

Returns the conversation labels (your categories) attached to a thread. Auth: Scope READ_UNIBOX · Org permission access_unibox.

ParameterInTypeDescription
thread_idquerystringRequired. The thread to read labels for. Also accepted as id.

The response wraps the labels in a data array.

Response

{
  "data": [
    { "id": "c0ffee00-0000-4000-8000-000000000001", "title": "Interested", "color": "#16a34a" }
  ]
}

Set thread labels

PUT /unibox/thread/labels

Replaces the full conversation-label set on a thread. The body's category_ids is the desired set, so the call is idempotent and retries are naturally safe. Only your own categories are attached. Auth: Scope WRITE_UNIBOX · Org permission access_unibox.

Request body

FieldTypeRequiredDescription
thread_idstringYesThe thread to label.
category_idsstring[]NoThe full desired set of category UUIDs. An empty array clears all labels.
{
  "thread_id": "thread-af83b21",
  "category_ids": [
    "c0ffee00-0000-4000-8000-000000000001",
    "c0ffee00-0000-4000-8000-000000000002"
  ]
}

Response

Returns the resulting label set in a data array.

{
  "data": [
    { "id": "c0ffee00-0000-4000-8000-000000000001", "title": "Interested", "color": "#16a34a" },
    { "id": "c0ffee00-0000-4000-8000-000000000002", "title": "Demo booked", "color": "#7c3aed" }
  ]
}

Mark messages seen

PATCH /unibox/seen

Marks a batch of messages as read or unread, org-wide. Up to 500 ids per call. Auth: Scope WRITE_UNIBOX · Org permission access_unibox.

Request body

FieldTypeRequiredDescription
email_idsstring[]YesMessage UUIDs to update (max 500).
seenbooleanNotrue marks as read, false marks as unread.
{
  "email_ids": [
    "9b6f0e2a-3c4d-4f1a-8b2e-1a2b3c4d5e6f",
    "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d"
  ],
  "seen": true
}

Response

Echoes the request back.

{
  "email_ids": [
    "9b6f0e2a-3c4d-4f1a-8b2e-1a2b3c4d5e6f",
    "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d"
  ],
  "seen": true
}

Reply from the inbox

POST /unibox/reply

Sends or schedules a reply from one of your mailboxes. The send is routed through the per-mailbox scheduler according to send_mode. Requires an active organization. Auth: Scope WRITE_UNIBOX · Org permission access_unibox.

Request body

FieldTypeRequiredDescription
email_account_idstringYesUUID of the sending mailbox.
tostring[]YesRecipient addresses (at least one).
ccstring[]NoCC addresses.
bccstring[]NoBCC addresses.
subjectstringYesSubject line.
body_htmlstringNoHTML body.
body_plainstringNoPlain-text body.
in_reply_tostring[]NoMessage-ID(s) this reply threads under.
thread_idstringNoThread to thread the reply into.
send_modestringNoinstant (default), smart (next mailbox gap), or scheduled (use scheduled_at).
scheduled_atstringNoRFC 3339 timestamp. Required when send_mode is scheduled; must be in the future.
{
  "email_account_id": "2a1b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "to": ["[email protected]"],
  "subject": "Re: Following up on pricing",
  "body_html": "<p>Happy to hop on a call this week.</p>",
  "in_reply_to": ["<[email protected]>"],
  "thread_id": "thread-af83b21",
  "send_mode": "instant"
}

Response

{
  "task_id": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
  "scheduled_at": "2026-06-11T14:30:00Z",
  "send_mode": "instant"
}

List active snoozes

GET /unibox/snoozes

Returns your active thread snoozes. Auth: Scope READ_UNIBOX · Org permission access_unibox.

The response wraps the snoozes in a data array.

Response

{
  "data": [
    {
      "id": "3a4b5c6d-7e8f-4a0b-9c1d-2e3f4a5b6c7d",
      "user_id": "1e2f3a4b-5c6d-7e8f-9a0b-1c2d3e4f5a6b",
      "thread_id": "thread-af83b21",
      "snoozed_until": "2026-06-12T09:00:00Z",
      "created_at": "2026-06-11T14:00:00Z",
      "updated_at": "2026-06-11T14:00:00Z"
    }
  ]
}

Snooze a thread

POST /unibox/snooze

Hides a thread from your inbox until snoozed_until passes. Upsert semantics: a second call on the same thread updates the time in place. Auth: Scope WRITE_UNIBOX · Org permission access_unibox.

Request body

FieldTypeRequiredDescription
thread_idstringYesThe thread to snooze.
snoozed_untilstringYesRFC 3339 timestamp to un-hide the thread.
{
  "thread_id": "thread-af83b21",
  "snoozed_until": "2026-06-12T09:00:00Z"
}

Response

{
  "id": "3a4b5c6d-7e8f-4a0b-9c1d-2e3f4a5b6c7d",
  "user_id": "1e2f3a4b-5c6d-7e8f-9a0b-1c2d3e4f5a6b",
  "thread_id": "thread-af83b21",
  "snoozed_until": "2026-06-12T09:00:00Z",
  "created_at": "2026-06-11T14:00:00Z",
  "updated_at": "2026-06-11T14:00:00Z"
}

Unsnooze a thread

DELETE /unibox/snooze

Un-snoozes a thread immediately. Idempotent: deleting a snooze that does not exist still succeeds with 204. Auth: Scope WRITE_UNIBOX · Org permission access_unibox.

ParameterInTypeDescription
thread_idquerystringRequired. The thread to un-snooze.

Response

204 No Content with an empty body.

List scheduled sends

GET /unibox/scheduled

Returns the outbound emails you have queued but not yet sent. Pass thread_id to scope to a single conversation (used to render queued replies inline); the response shape is identical either way. Auth: Scope READ_UNIBOX · Org permission access_unibox.

ParameterInTypeDescription
thread_idquerystringOptional. Restrict to scheduled sends queued into one thread.

The response wraps the items in a data array. Each item is a preview of the queued message.

Response

{
  "data": [
    {
      "task_id": "5c6d7e8f-9a0b-4c1d-8e2f-3a4b5c6d7e8f",
      "scheduled_at": "2026-06-12T09:15:00Z",
      "created_at": "2026-06-11T14:30:00Z",
      "account_id": "2a1b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
      "account_email": "[email protected]",
      "account_name": "Sales",
      "to": ["[email protected]"],
      "subject": "Re: Following up on pricing",
      "snippet": "Happy to hop on a call this week.",
      "thread_id": "thread-af83b21"
    }
  ]
}

Cancel a scheduled send

DELETE /unibox/scheduled/:task_id

Cancels a pending scheduled send before it fires. The queued task is marked cancelled and short-circuits to a no-op when its run time arrives. Auth: Scope WRITE_UNIBOX · Org permission access_unibox.

ParameterInTypeDescription
task_idpathstringUUID of the scheduled task to cancel.

Response

204 No Content with an empty body.

Get a message by id

GET /unibox/:id

Returns a single message by its UUID, including the full envelope and body. Auth: Scope READ_UNIBOX · Org permission access_unibox.

ParameterInTypeDescription
idpathstringUUID of the message.

Response

{
  "id": "9b6f0e2a-3c4d-4f1a-8b2e-1a2b3c4d5e6f",
  "gmail_id": "18f0c2a9b7d4e5f6",
  "uid": 4821,
  "parent_id": "<[email protected]>",
  "thread_id": "thread-af83b21",
  "flags": ["\\Seen"],
  "bcc": [],
  "cc": [],
  "date": "2026-06-11T14:22:00Z",
  "from": ["Jane Doe <[email protected]>"],
  "in_reply_to": ["<[email protected]>"],
  "message_id": "<[email protected]>",
  "ReplyTo": [],
  "to": ["[email protected]"],
  "subject": "Re: Following up on pricing",
  "size": 18422,
  "internal_date": "2026-06-11T14:22:09Z",
  "mod_seq": 90210,
  "body_plain": "Thanks for the details...",
  "body_html": "<p>Thanks for the details...</p>"
}

Errors

All error responses follow the standard envelope. See error codes for the full list.

{
  "error": "forbidden",
  "message": "Unibox requires an active trial or paid subscription",
  "code": "FORBIDDEN",
  "request_id": "req_8f3a1c2b9d"
}

Common cases: 403 when the organization lacks unified-inbox access, 400 for a missing thread_id, an invalid cursor or limit, or no organization selected, and 400 when marking more than 500 messages seen in one call.

On this page