Inbox

The Inbox API exposes Warmerly's unified inbox — every email, LinkedIn DM, and WhatsApp message tied to a contact, in one place. Use it to list contacts, pull a full conversation timeline, and send replies across channels.

Inbox endpoints only need X-Api-Key. They do not read X-Project-Id — unlike most other resources, the inbox is scoped to your active workspace (the same one selected in the dashboard), which can span multiple projects at once. If you're a member of more than one workspace, requests use whichever workspace is currently active on your account.

curl https://app.warmerly.com/api/v1/inbox/contacts \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

List contacts

GET /inbox/contacts

Returns every inbox contact in your active workspace — the people/companies you've exchanged messages with across email, LinkedIn, and WhatsApp — newest activity first.

Query paramRequiredDescription
workspaceIdNoFilter to contacts belonging to a single project. Despite the name, this is a project ID (the inbox filter bar labels projects as "workspaces").
accountIdNoFilter to contacts with at least one message sent/received via this account (email account today).
qNoSearch contact name/email/phone/company and message content (subject, body, sender). Terms match by prefix.
needsReplyNotrue — only conversations classified as a lead whose newest message is inbound.
archivedNotrue — only conversations where every message is archived.
unreadNotrue — only conversations with at least one unread inbound message.
categoryNolead, not_relevant, or spam.
channelNoemail, linkedin, or whatsapp.
limitNoPage size, 1–200 (default 50).
offsetNoPage offset (default 0).

All filters are applied before pagination, so total is the real number of matching conversations and paging through with offset reaches every one of them.

curl "https://app.warmerly.com/api/v1/inbox/contacts?accountId=8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a" \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
  "contacts": [
    {
      "id": "c3a1f6e2-9c4d-4e2a-8b5f-6a2c1d9e0f7b",
      "name": "Jamie Rivera",
      "company": "Northwind Traders",
      "avatarUrl": null,
      "email": "jamie@northwindtraders.com",
      "phone": "+15551234567",
      "hasWhatsapp": true,
      "linkedinProfileUrl": "https://www.linkedin.com/in/jamierivera",
      "channels": ["email", "linkedin"],
      "lastActivityAt": "2026-07-01T16:42:03.000Z",
      "hasUnread": true,
      "lastPreview": "Thanks for reaching out — can we do a call next week?",
      "category": "lead",
      "categoryReason": "Replied positively and asked to schedule a call"
    }
  ],
  "total": 128,
  "hasMore": true
}
FieldTypeDescription
idstring (UUID)Contact ID — use this for the timeline and reply endpoints.
namestringDisplay name, falling back to email or phone if unset.
companystring | nullCompany name, when known from an associated campaign lead.
emailstring | nullContact's email address.
phonestring | nullContact's phone number, when known.
hasWhatsappbooleanTrue if the contact has a phone number or a raw WhatsApp JID on file. Use this (not phone) to decide whether a WhatsApp reply is possible.
linkedinProfileUrlstring | nullLinkedIn profile URL, when known.
channelsarrayChannels (email, linkedin, whatsapp) this contact has any message on.
lastActivityAtstringISO 8601 timestamp of the most recent message.
hasUnreadbooleanTrue if there's at least one unread inbound message.
lastPreviewstring | nullSnippet of the most recent message.
category"lead" | "not_relevant" | "spam" | nullAI/rule-assigned triage category.
categoryReasonstring | nullShort explanation for the assigned category.

Get a contact's timeline

GET /inbox/contacts/{contactId}/timeline

Returns the full conversation with a contact across all channels, in chronological order, plus any autosaved email draft in progress for the latest inbound thread.

curl https://app.warmerly.com/api/v1/inbox/contacts/c3a1f6e2-9c4d-4e2a-8b5f-6a2c1d9e0f7b/timeline \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
  "items": [
    {
      "id": "9d1e0b7a-5e7b-4f10-b2aa-1c8f0d3e6a4b",
      "channel": "email",
      "direction": "inbound",
      "status": "sent",
      "content": "Hi, thanks for the intro — happy to learn more.",
      "bodyHtml": "<p>Hi, thanks for the intro — happy to learn more.</p>",
      "subject": "Re: Quick question about your workflow",
      "sentAt": "2026-06-30T11:05:00.000Z",
      "fromAddr": "jamie@northwindtraders.com",
      "fromName": "Jamie Rivera",
      "toAddrs": ["you@yourcompany.com"],
      "externalId": null,
      "error": null
    },
    {
      "id": "7f3a2cdb-9e1a-4f0c-8b6d-2e5f7a1c3b9d",
      "channel": "email",
      "direction": "outbound",
      "status": "sent",
      "content": "Great — does Tuesday at 3pm work for a quick call?",
      "bodyHtml": null,
      "subject": "Re: Quick question about your workflow",
      "sentAt": "2026-07-01T16:42:03.000Z",
      "fromAddr": "you@yourcompany.com",
      "fromName": "Your Name",
      "toAddrs": ["jamie@northwindtraders.com"],
      "externalId": null,
      "error": null
    }
  ],
  "draft": null,
  "contact": {
    "id": "c3a1f6e2-9c4d-4e2a-8b5f-6a2c1d9e0f7b",
    "category": "lead",
    "categoryReason": "Replied positively and asked to schedule a call"
  }
}
FieldTypeDescription
items[].channel"email" | "linkedin" | "whatsapp"Channel the message was sent/received on.
items[].direction"inbound" | "outbound"Who sent it.
items[].status"queued" | "sent" | "failed"Delivery status (WhatsApp sends start queued until the worker delivers them).
items[].contentstringPlain-text body (falls back to snippet if the full body wasn't stored).
draftobject | nullAutosaved email draft for the latest inbound email thread, if one exists (subject, bodyText).

If contactId doesn't exist, or belongs to a contact outside your active workspace, this returns 404:

{ "error": { "code": "not_found", "message": "not_found", "details": null } }

Reply to a contact

POST /inbox/contacts/{contactId}/reply

Sends a reply to a contact on a specific channel and appends it to the timeline. Each channel has its own requirements:

FieldTypeRequiredDescription
channel"email" | "linkedin" | "whatsapp"YesChannel to send on.
bodystringYesMessage text.
subjectstringEmail onlyRequired when channel is "email".
sourceAccountIdstringYesID of the connected account to send from (email account, LinkedIn account, or WhatsApp account, matching channel).
curl -X POST https://app.warmerly.com/api/v1/inbox/contacts/c3a1f6e2-9c4d-4e2a-8b5f-6a2c1d9e0f7b/reply \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
        "channel": "email",
        "subject": "Re: Quick question about your workflow",
        "body": "Great — does Tuesday at 3pm work for a quick call?",
        "sourceAccountId": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a"
      }'
{
  "message": {
    "id": "7f3a2cdb-9e1a-4f0c-8b6d-2e5f7a1c3b9d",
    "channel": "email",
    "direction": "outbound",
    "status": "sent",
    "contactId": "c3a1f6e2-9c4d-4e2a-8b5f-6a2c1d9e0f7b",
    "accountId": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
    "subject": "Re: Quick question about your workflow",
    "bodyText": "Great — does Tuesday at 3pm work for a quick call?",
    "toAddrs": ["jamie@northwindtraders.com"],
    "fromAddr": "you@yourcompany.com",
    "receivedAt": "2026-07-01T16:42:03.000Z"
  }
}

Notes on behavior by channel:

  • Email sends synchronously over SMTP; the contact must have an email on file and subject is required. A send failure returns 500 with the SMTP error message, but the failed message is still recorded in the timeline with status: "failed".
  • LinkedIn sends synchronously via Unipile; the contact must have a linkedinProfileId on file.
  • WhatsApp is sent asynchronously — the message is inserted with status: "queued" and delivered by a background worker shortly after. The contact must have a phone number or WhatsApp JID on file.

Missing channel-specific data returns 400, e.g.:

{ "error": { "code": "bad_request", "message": "contact_has_no_email", "details": null } }

List threads

GET /inbox/threads

Returns email threads (grouped by threadKey + accountId) with aggregate counts, for building a mailbox-style view. This is account/folder-oriented, as opposed to /inbox/contacts, which is contact-oriented and spans channels.

Query paramRequiredDescription
accountIdNoFilter to a single email account.
folderNoIMAP folder to filter by. Defaults to INBOX. Ignored unless view is inbox.
viewNoOne of inbox (default), archived, starred, sent.
qNoFull-text search across subject, sender, and body (Postgres FTS with an ILIKE fallback).
limitNoMax threads to return, 1100. Defaults to 50.
curl "https://app.warmerly.com/api/v1/inbox/threads?accountId=8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a&view=inbox&limit=20" \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
  "threads": [
    {
      "threadKey": "<CADx...@mail.gmail.com>",
      "accountId": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
      "lastReceivedAt": "2026-07-01T16:42:03.000Z",
      "messageCount": 4,
      "unreadCount": 1,
      "starred": false,
      "lastFromAddr": "jamie@northwindtraders.com",
      "lastFromName": "Jamie Rivera",
      "lastSubject": "Re: Quick question about your workflow",
      "lastSnippet": "Great — does Tuesday at 3pm work for a quick call?"
    }
  ]
}

Get a thread

GET /inbox/threads/{key}

Returns every raw message in a single email thread, chronological order. {key} is the threadKey from /inbox/threads (URL-encode it — thread keys are typically Message-Id-style strings and contain special characters).

Query paramRequiredDescription
accountIdNoNarrow to messages on this account (recommended — thread keys aren't guaranteed unique across accounts).
curl "https://app.warmerly.com/api/v1/inbox/threads/%3CCADx...%40mail.gmail.com%3E?accountId=8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a" \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
  "messages": [
    {
      "id": "9d1e0b7a-5e7b-4f10-b2aa-1c8f0d3e6a4b",
      "accountId": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
      "folder": "INBOX",
      "threadKey": "<CADx...@mail.gmail.com>",
      "fromAddr": "jamie@northwindtraders.com",
      "fromName": "Jamie Rivera",
      "toAddrs": ["you@yourcompany.com"],
      "subject": "Re: Quick question about your workflow",
      "bodyText": "Hi, thanks for the intro — happy to learn more.",
      "isRead": true,
      "isStarred": false,
      "isArchived": false,
      "channel": "email",
      "direction": "inbound",
      "status": "sent",
      "receivedAt": "2026-06-30T11:05:00.000Z"
    }
  ]
}

This endpoint returns raw inbox_messages rows (unfiltered by workspace/project ownership) rather than the trimmed shape used by the timeline endpoint — scope your query with accountId to keep results to accounts you control.

Filter options

GET /inbox/filter-options

Returns the projects and email accounts available to filter the inbox by, for populating a workspace/account picker in your own UI.

curl https://app.warmerly.com/api/v1/inbox/filter-options \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
  "workspaces": [
    { "id": "b2f4a6c1-3e5d-4f7a-9b1c-0d2e4f6a8c1b", "name": "Marketing outreach" }
  ],
  "accounts": [
    {
      "id": "8f14e45f-ceea-4c6a-8c8d-6b1a5b5c9c1a",
      "email": "you@yourcompany.com",
      "provider": "gmail",
      "projectId": "b2f4a6c1-3e5d-4f7a-9b1c-0d2e4f6a8c1b"
    }
  ]
}

workspaces here is a list of projects within your active workspace (the workspaceId query param used elsewhere in this API is a project ID — see the note under List contacts).

Other endpoints

These cover more specialized inbox workflows and are not detailed here:

EndpointDescription
POST /inbox/composeStart a new (non-reply) conversation over email, LinkedIn, or WhatsApp.
POST /inbox/syncTrigger an IMAP sync for one account (?accountId=) or all connected accounts.
POST /inbox/contacts/{contactId}/draft-replyGenerate an AI draft reply for a contact's latest inbound email and autosave it.
GET/POST /inbox/templates, PATCH/DELETE /inbox/templates/{id}Reusable reply templates.
GET/POST /inbox/drafts, PATCH/DELETE /inbox/drafts/{id}Manually saved (non-autosaved) email drafts.
GET /inbox/warmup-jobsWarmup job activity feed for the inbox view.
PATCH/DELETE /inbox/messages/{id}Update flags on (read/starred/archived), or delete, a single message.
GET /inbox/messages/{id}/attachments/{part}Download a specific email attachment part.