Appearance
Behavior & limits
How the SDK behaves at runtime — worth understanding before you ship.
Message delivery (polling)
There is no WebSocket. The SDK polls the backend (default every 4s, pollingInterval). When a poll fails (network/timeout/server unavailable) the interval backs off exponentially (4s → 8s → 16s → max 30s) and resets to the base on the next success. Tickets are polled less often (~every 20s) since they change rarely; webhooks cover changes in between.
Message statuses
User messages show delivery ticks:
| Tick | Meaning |
|---|---|
| 🕓 | sending |
| ✓ | sent / delivered to the backend |
| ✓✓ (tinted) | seen — an operator replied after this message |
| ⚠ (red) | failed — the send didn't go through (tap to retry) |
"Seen" and "typing" caveat
Intercom's API and webhooks expose no real read-receipt or "operator typing" signal, and the architecture forbids WebSocket. So:
sent/deliveredare real (the backend accepted the message).seenis inferred honestly: a message is "seen" once an operator message follows it.- A live typing indicator is not available. (Mock mode simulates one for the showcase only.)
Error states
The SDK never shows a blank screen. It renders a localized panel with a recovery action for:
- Session expired (401) → calls
onSessionExpired, offers re-login. - Offline (web:
navigator.onLine) → auto-recovers on reconnect. - Support unavailable (backend/Intercom down) → retry.
Resilience (backend)
- Circuit breaker around Intercom: after 3 consecutive outages it opens for 30s and fails fast (502), then probes with one trial request.
- Rate limit: per-user limit on
/api/*(429 +Retry-After). - Read coalescing cache: concurrent polls (and a user's multiple devices) share one Intercom call.
Scale
Intercom allows ~10k API calls/min per app. With 4s polling that's ~15 calls/min per active user — roughly a 660 concurrent-user ceiling before the caching and ticket-poll decoupling matter. Beyond a single backend instance, move the read cache and rate-limit store to Redis (see Deployment).
Request lifecycle
The user sees one continuous thread, but under the hood each support request is its own Intercom conversation with a state (open / closed / snoozed).
- No open request → the user first picks a topic (admin-managed main → sub "reasons"). Picking a leaf topic starts the request immediately — no typed message — and the thread opens with the operator acknowledgement.
- Open request → the normal composer; messages reply into that conversation.
- Closed (by the operator, or by the user via the close button →
POST /api/conversations/:id/close) → a "request closed" line appears with an inline CSAT rating. Submitting tags the conversationCSAT, posts a note, and stores the stars — which persist and show on reopen. - A new request after a close appends below the previous (closed) messages — the history stays; it's still one thread.
Live state (no reload)
The message poll also returns the conversation's current state / rated, so when an operator closes or reopens a request, the UI reflects it within one poll interval (~4s) without a reload or WebSocket.
Single thread, many tickets
There is one ongoing support thread per user (made of sequential requests), and any number of tickets. Replying to a resolved ticket reopens it automatically.