Widget de chat --- Next.js (`@mailoo/chat`)

Última actualización: Oct 12, 2026Sección: Integraciones

Expón chat del sitio desde un host Next.js sin poner una clave API en el navegador. Usa @mailoo/chat para rutas BFF y el widget flotante opcional. Contrato de la API y notas de WebSocket: chat-widget{.interpreted-text role="doc"}, chat-websocket-production{.interpreted-text role="doc"}.

Flujo

  1. El navegador llama solo a rutas del mismo origen en tu aplicación Next.js:
  • POST /api/v1/webhooks/chat/submit --- cuerpo { "content": "...", "sessionPublicId?": "..." }
  • GET /api/v1/webhooks/chat/messages?sessionPublicId=... --- polling opcional (since soportado)
  • GET /api/v1/webhooks/chat/status --- estado opcional de la integración
  1. El controlador de ruta reenvía a Mailoo con X-API-Key del entorno del servidor (createChatSubmitHandler / createChatMessagesHandler / createChatStatusHandler de @mailoo/chat/routes). Opcionalmente inyecta senderEmail / senderName mediante resolveSender para que el panel muestre la identidad del visitante en lugar de Guest.
  2. Opcional: el widget abre WebSocket hacia la API. El host se resuelve en tiempo de ejecución en el servidor (sin NEXT_PUBLIC_* para el origen de la API):
  • Orden por defecto: MAILOO_CHAT_WS_ORIGIN → MAILOO_CHAT_INTEGRATION_API → API_BASE_URL (getMailooChatWebSocketOrigin de @mailoo/chat)
  • Establece MAILOO_CHAT_WS_ORIGIN cuando el nombre de host de la API visible para el navegador difiere de la URL que usa el servidor Node para llamar a la API

Superficie del paquete

// app/api/v1/webhooks/chat/submit/route.ts
import { createChatSubmitHandler } from '@mailoo/chat/routes'

export const POST = createChatSubmitHandler({
  resolveSender: async () => {
    const session = await auth()
    return session?.user?.email
      ? { email: session.user.email, name: session.user.name ?? undefined }
      : null
  },
})
// app/api/v1/webhooks/chat/messages/route.ts
import { createChatMessagesHandler } from '@mailoo/chat/routes'
export const GET = createChatMessagesHandler()
// app/api/v1/webhooks/chat/status/route.ts
import { createChatStatusHandler } from '@mailoo/chat/routes'
export const GET = createChatStatusHandler()

Sesión headless (UI personalizada) mediante @mailoo/chat/hooks:

import { useMailooChatSession } from '@mailoo/chat/hooks'

const chat = useMailooChatSession({
  apiOrigin,
  projectUid,
  integrationId,
  isAuthenticated,
  enabled: panelOpen,
})

Variables de entorno (solo servidor)

MAILOO_CHAT_INTEGRATION_API=https://api.mailoo.app
MAILOO_CHAT_INTEGRATION_API_KEY=your-api-key-here
MAILOO_CHAT_INTEGRATION_PROJECT_UID=your-project-uid
MAILOO_CHAT_INTEGRATION_ID=your-jsbox-integration-id
MAILOO_CHAT_WELCOME_MESSAGE=Hello! How can we help?

# Origen público opcional de la API para ws/wss cuando difiere de la URL de la API del servidor


# MAILOO_CHAT_WS_ORIGIN=https://api.mailoo.app

La clave API necesita chat.send-message (o FULL). Copia projectUid e integrationId desde Conexión y ajustes / fragmento de integración del panel de control.

Widget flotante

Importa el CSS del shell (obligatorio para que el FAB quede fijo aunque Tailwind no escanee @mailoo/chat). En este monorepo PostCSS resuelve mejor una ruta relativa:

@import '../../../../packages/chat/src/styles/widget.css';
/* paquete publicado: @import '@mailoo/chat/styles'; */

Recomendado (ruta por defecto): resuelve la config en el servidor y monta MailooSiteChatShell. Marca, temas, posición, bienvenida, privacidad y operadores se cargan siempre desde JSBOX config.chatWidget:

import { resolveMailooChatHostConfig } from '@mailoo/chat'
import { MailooSiteChatShell } from '@mailoo/chat/client'

const chat = await resolveMailooChatHostConfig({
  locale,
  isAuthenticated: Boolean(session?.user?.email),
})

{chat ? (
  <MailooSiteChatShell
    chat={chat}
    unavailableAction={{
      type: 'mailto',
      email: 'support@example.com',
    }}
  />
) : null}

resolveMailooChatHostConfig devuelve null si falta env MAILOO_CHAT_* o no se puede cargar widget-config (errores HTTP incluido 429, red, payload inválido). Nunca lanza --- la página sigue HTTP 200 y omite el shell del chat. Llama openMailooSiteChat() desde cualquier componente cliente para abrir el panel.

UI personalizada: pasa widgetConfig a MailooSiteChatWidget tú mismo (desde fetchMailooChatWidgetConfig o BFF GET …/webhooks/chat/widget-config). El launcher es un FAB fijo (esquinas o middle-left / middle-right) con Heroicons.

Chat no disponible / límite de peticiones

El paquete clasifica fallos de transporte y servicio al enviar o sondear mensajes (no muestra el texto crudo de la API en la UI):

  • rate_limited --- HTTP 429 (límite por minuto de la clave API)
  • unavailable --- red, BFF 502/**503** u otros 5xx

Errores de validación, sesión cerrada (409) y sesión inexistente (404, se limpia sola) siguen siendo errores inline --- no abren el panel de indisponibilidad.

resolveMailooChatHostConfig devuelve null (no montes el chat) si no carga widget-config. Así la página del host sigue arriba; el panel de indisponibilidad solo aplica después de montar el widget, cuando falla send/poll.

El integrador elige la acción (correo, página de soporte o UI propia):

<MailooSiteChatShell
  chat={chat}
  unavailableAction={{ type: 'mailto', email: 'support@example.com' }}
  // o: { type: 'link', href: '/support', label: 'Contactar soporte' }
  // o: renderUnavailable={({ fault, labels, retry }) => ...}
/>

Sin unavailableAction, el visitante ve la explicación y Reintentar (borra el fault y reanuda el polling). Conviene pasar un contacto. Headless: fault / onFault / retryAfterFault en useMailooChatSession.

Dogfood de mailoo.app: el sitio monta MailooSiteChatShell en cada layout de locale cuando están definidas MAILOO_CHAT_*, con unavailableAction → mailto:support@mailoo.app. La tarjeta Soporte → Chat en vivo abre el mismo panel. Sin esas variables la tarjeta queda en «Próximamente» y no se muestra el launcher.

Consola de chat del panel de control

Para integraciones JSBOX, Abrir consola de chat abre la UI del operador para esa integración. Los operadores usan la sesión del panel; los visitantes usan el webhook público y las rutas WebSocket de arriba.

Relacionado

  • chat-widget{.interpreted-text role="doc"} --- webhooks públicos y alcances
  • chat-websocket-production{.interpreted-text role="doc"} --- notas de WebSocket en producción
  • nextjs-packages{.interpreted-text role="doc"} --- resumen de paquetes