Conversations & Reply SLA

Quolle automatically threads the inbound mail you receive with the replies you send, and tracks your First Response Time against a target you set. Use it to see which customer emails are still waiting on a reply — and which have breached your SLA.

Conversations are built on top of Inbound Email. They appear automatically once you have an inbound route receiving mail — there's nothing extra to turn on.

How threading works

When an inbound email arrives on one of your routes, Quolle groups it into a conversation by the sender's address and the normalized subject (so "Re: Where is my order?" threads onto the original "Where is my order?"). The clock starts on that first inbound message.

When you reply — by sending a single-recipient email back to that same person with the same subject — Quolle records it as your response, stops the clock, and marks the conversation replied. A new inbound message on a replied thread reopens it.

First Response Time (FRT) is the industry-standard support metric: the time between the first inbound message and your first reply. A conversation still awaiting a reply past its SLA target is flagged as breached.

Setting the SLA target

The SLA is configured per inbound route, in hours, when you create the route. It defaults to 24 hours — the common target for email support.

curl -X POST https://api.quolle.com/v1/inbound/routes \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "matchAddress": "[email protected]",
    "webhookUrl": "https://yourapp.com/inbound/quolle",
    "slaHours": 4
  }'

slaHours accepts 1–168 (up to a week). You can also set it from the Settings → Inbound tab in your dashboard.

Authentication

The conversation endpoints are authenticated with your dashboard session (a JWT bearer token), the same as inbound route management — not an API key. They power the dashboard's Conversations view.

List conversations

GET /v1/conversations

QueryDescription
statusFilter by awaiting_reply, replied, or closed
limitPage size, max 100 (default 50)
offsetPagination offset (default 0)
// Response 200
{
  "conversations": [
    {
      "id": "uuid",
      "participant": "[email protected]",
      "subject": "Where is my order?",
      "status": "awaiting_reply",
      "firstInboundAt": "2026-07-14T10:03:00.000Z",
      "lastInboundAt": "2026-07-14T10:03:00.000Z",
      "firstReplyAt": null,
      "firstResponseSeconds": null,
      "slaSeconds": 86400,
      "slaBreached": false,
      "messageCount": 1,
      "updatedAt": "2026-07-14T10:03:00.000Z",
      "awaitingReply": true,
      "secondsAwaiting": 5400,
      "breached": false
    }
  ],
  "total": 1,
  "counters": { "awaiting": 1, "breached": 0 }
}
FieldDescription
statusawaiting_reply, replied, or closed
firstResponseSecondsTime to your first reply, set once you respond
slaSecondsThe route's SLA target in seconds
slaBreachedFrozen result once replied — whether FRT exceeded the SLA
awaitingReplyLive: is the thread currently waiting on you
secondsAwaitingLive: how long the newest unanswered message has waited
breachedLive: past SLA and still awaiting (or the frozen result once replied)

counters.breached counts every awaiting conversation past its own SLA target — across all pages, not just the returned page.

Get one conversation

GET /v1/conversations/:id — returns the conversation plus its full message thread.

// Response 200
{
  "conversation": { "id": "uuid", "participant": "[email protected]", "status": "replied", "...": "" },
  "messages": [
    {
      "id": "uuid",
      "direction": "inbound",
      "fromAddr": "[email protected]",
      "toAddr": "[email protected]",
      "subject": "Where is my order?",
      "snippet": "Hi, order ORD-8821 hasn't arrived…",
      "messageId": "<[email protected]>",
      "emailId": null,
      "createdAt": "2026-07-14T10:03:00.000Z"
    },
    {
      "id": "uuid",
      "direction": "outbound",
      "toAddr": "[email protected]",
      "subject": "Re: Where is my order?",
      "emailId": "the-sent-email-id",
      "createdAt": "2026-07-14T11:20:00.000Z"
    }
  ]
}

Outbound messages carry emailId, linking back to the sent email in your logs. Only a short snippet of the body is stored.

Close or reopen

PATCH /v1/conversations/:id

curl -X PATCH https://api.quolle.com/v1/conversations/<id> \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{ "status": "closed" }'

Set status to closed to resolve a thread, or awaiting_reply to reopen it.