Chat widget (JSBOX)

Last updated: Sep 13, 2026Section: Integrations

The JSBOX integration type powers website chat: visitors send messages that appear in your Mailoo dashboard. You can reply from the chat console; visitors see updates over WebSocket or polling.

::: {.contents local=""} :::

Concepts

  • Integration type: JSBOX (created in the dashboard or via the public API with an API key).
  • Sessions: Each visitor thread is a chat session (opaque sessionPublicId). The first message creates a session; later messages include sessionPublicId.
  • API key: Required for server-side calls to Mailoo webhooks (your BFF, backend, or automation). Never put the API key in browser JavaScript.

Public API (X-API-Key)

Restricted keys can include:

  • webhook.chat-integration-create --- POST /api/v1/webhooks/chat/{projectUid}/integrations (create a JSBOX integration).
  • chat.send-message --- chat messages, session history, and chat status under /api/v1/webhooks/chat/....

Create a JSBOX integration

curl -X POST "https://api.mailoo.app/api/v1/webhooks/chat/proj_1/integrations" \\
     -H "X-API-Key: YOUR_KEY" \\
     -H "Content-Type: application/json" \\
     -d '{"name":"Support chat","config":{"allowedOrigins":["https://example.com"]}}'

Send a visitor message (scoped to project + integration):

curl -X POST "https://api.mailoo.app/api/v1/webhooks/chat/{projectUid}/{integrationId}/messages" \\
     -H "X-API-Key: YOUR_KEY" \\
     -H "Content-Type: application/json" \\
     -H "Origin: https://your-site.com" \\
     -d '{"content":"Hello","sessionPublicId":"optional-if-continuing"}'

List messages in a session (for server/BFF polling):

GET /api/v1/webhooks/chat/{projectUid}/{integrationId}/sessions/{sessionPublicId}/messages?since=ISO8601

Chat integration status

GET /api/v1/webhooks/chat/{projectUid}/{integrationId}/status

Legacy path (integration id only): POST /api/v1/webhooks/messages/{integrationId} still exists but requires chat.send-message and a JSBOX integration.

Public chat webhooks share the API-key rate limit. When the limit is exceeded, Mailoo responds with HTTP 429. The @mailoo/chat widget classifies that (and gateway/network failures) and shows an unavailable panel instead of raw API text --- the host should pass unavailableAction (see chat-widget-nextjs-example{.interpreted-text role="doc"}).

Dashboard (Bearer JWT)

  • Sessions: GET /api/v1/chat/{integrationId}/sessions --- optional status=OPEN|PENDING|CLOSED and assignedUserId=<userId> or assignedUserId=unassigned.
  • History: GET /api/v1/chat/{integrationId}/sessions/{sessionPublicId}/messages
  • Owner reply: POST /api/v1/chat/{integrationId}/sessions/{sessionPublicId}/reply with JSON {"content":"..."}.
  • Assign / close: PATCH /api/v1/chat/{integrationId}/sessions/{sessionPublicId} with {"assignedUserId":"<userId>"|null} (project members only) or {"action":"close"}.

Sessions are OPEN, PENDING, or CLOSED. Widget settings on the JSBOX integration (config.chatWidget) drive:

  • Autoresponder --- first visitor message on a new session sends autoresponder.message; optional expectedReplyHours sets status PENDING until an operator replies.
  • Reply templates --- replyTemplates (not FORM campaign templates); the console inserts them into the reply box.
  • Close follow-up --- on action: close, closeFollowUp.message and/or closeFollowUp.reviewUrl are sent to the visitor. Further visitor messages on a closed session return 409.
  • Optional AI agent --- config.chatWidget.agent (enabled, name, optional imageMediaId, kbIntegrationId for a Blog in the same project, plus markdown instructions, kbDigestPrompt, kbArticlePrompt, and kbFiles). When enabled, the agent appears first in the widget team roster (synthetic operator id jsbox_chat_agent). Mailoo API prepends instructions and fills the digest/article templates (placeholders {{kb_digest}}, {{visitor_message}}, {{article_title}}, {{article_slug}}, {{article_body}}) then calls Wednesday in the background after inbound with a KB digest (published titles + slugs only); the agent may request one article body via KB_REQUEST:<slug>. Knowledge-base files are stored on S3 and listed on the agent; they are not injected into the model yet. The widget never talks to Wednesday. Agent replies are OUTBOUND with metadata.source: jsbox_chat_agent and senderName from the agent display name. Assigning the session to an operator stops the agent. The agent may also hand off to a human via HANDOFF_TO_HUMAN: (visitor-facing line plus operator brief); Mailoo sets handedOffAt, status PENDING, stores the brief as OUTBOUND metadata.source: jsbox_chat_agent_handoff (dashboard only), and skips further agent turns. Requires API WEDNESDAY_BASE_URL (dev: http://127.0.0.1:4080) when enabled.
  • MCP / agent settings --- GET/PATCH /api/v1/chat/{projectUid}/integrations/{integrationId}/settings (scope chat.manage or MCP token) reads/updates the same sanitized widget config and returns lifetime counts for messages and sessions (no session list or message bodies). MCP tool: manage_jsbox_integration_settings (see mailoo-mcp{.interpreted-text role="doc"}). Agent skill (minimum rules): configure-chat-mcp{.interpreted-text role="doc"}.

The web app proxies these through its BFF under the same paths (with session auth).

WebSocket

On a Next.js host using @mailoo/chat, the browser WebSocket origin is not a NEXT_PUBLIC_* build-time variable: the server resolves it at runtime (MAILOO_CHAT_WS_ORIGIN, else chat/API base env --- see getMailooChatWebSocketOrigin).

  • Owner (dashboard): /api/v1/chat/ws?integrationId=...&token=<JWT> --- use the same access token as Authorization: Bearer.
  • Visitor: /api/v1/chat/visitor-ws?projectUid=...&integrationId=...&sessionPublicId=... --- no API key; the session must already exist (created by posting a message). Prefer exposing only sessionPublicId to the browser and validating on your BFF when possible.

For architecture details, event payloads, reverse proxy (wss), and multi-replica caveats, see chat-websocket-production{.interpreted-text role="doc"}.

Dashboard (integration detail)

On the JSBOX integration page, project owners use tabs:

  • Widget --- brand colours, welcome copy, privacy URL, launcher position (four corners plus middle-left / middle-right; default bottom-right), and topic chips.
  • Replies & automation --- operator visibility, first-message autoresponder, reply templates, and close-session follow-up.
  • AI Agent --- owner-only settings for config.chatWidget.agent: enable toggle, display name, optional avatar, Blog KB, markdown instruction windows, and knowledge-base file upload (S3). Editors can open the tab; only the owner can save. When enabled, the agent is listed in the widget team and in Setup → Operators (not toggleable there). The mailoo.app floating chat (MailooSiteChatShell / MAILOO_CHAT_*) is this same JSBOX; dev seed enables the agent on mailoo-website with name, Blog KB, and default prompt templates (requires Wednesday + WEDNESDAY_BASE_URL).
  • Connection & settings --- images library, API keys, server env snippet, and delete. Raw stored config is available behind View configuration JSON (pretty-printed reference dialog), not as an always-visible block.

There is no Products/content tab for JSBOX --- status, message count, and last activity stay in the page header. Editors see those header stats, Connection & settings, and the AI Agent tab (toggle disabled). Widget/replies editors remain owner-only.

CORS and allowedOrigins

Browser calls to Mailoo webhooks may send an Origin header. Optionally configure allowed origins on the JSBOX integration. If the list is empty, Origin is not checked (API key still required). Legacy domain in stored config remains a CORS fallback for older rows but is no longer collected in the dashboard.

Launcher position is stored as config.chatWidget.position and returned on the public widget-config endpoint. The default host path (resolveMailooChatHostConfig + MailooSiteChatShell) always applies it via widgetConfig. An explicit position prop on MailooSiteChatWidget still overrides the stored value when set.

See also

  • chat-widget-nextjs-example{.interpreted-text role="doc"} --- Next.js BFF pattern and env variables
  • chat-websocket-production{.interpreted-text role="doc"} --- WebSocket behaviour and production notes
  • OpenAPI: https://api.mailoo.app/docs/v1