channel docs

channel / components / channel-message

ChannelMessageBubble

The simplest way to think about it: one message, rendered the same way no matter the platform. It takes a canonical channel_messages record and draws the bubble: inbound on the left, outbound on the right, with the author, the platform badge, status and time in the header.

Preview

+54 9 11 5555-0147whatsapp04:31 PM

Hola! Necesito cotizar 200 cascos certificados.

agentwhatsappdelivered04:33 PM

Tomo el pedido: 200 cascos IRAM 3620. ¿Fecha límite?

Install

terminal
pnpm dlx shadcn@latest add https://registry.ekairos.dev/r/channel-message.json

Start simple: one inbound message

Hand it a message and it figures out the rest. direction decides the side, role decides the tone, and the badge tells the platform:

basic
import { ChannelMessageBubble } from "@/components/ekairos/channel/channel-message";

<ChannelMessageBubble
  message={{
    id: "m1",
    channel: "whatsapp",
    direction: "inbound",
    role: "user",
    participant: "+54 9 11 5555-0147",
    text: "Hola! Necesito cotizar 200 cascos certificados.",
    createdAt: new Date().toISOString(),
  }}
/>

Then: across channels

The same component renders an email reply, a slack approval, an outbound RFQ — because it reads the canonical model, not provider payloads. And when your whole view is a single channel, pass hideChannel to drop the badge (the email below does exactly that):

ventas@acme.comemail05:07 PM

Cotizamos USD 11.40/u, entrega 10 días hábiles.

laura (compras)slack05:16 PM

Aprobado 👍 generá la OC.

ventas@acme.comsent04:49 PM

Subject: RFQ — 200 cascos certificados Solicitamos cotización por 200 unidades.

What it actually renders

The body comes from the canonical content: text when present, otherwise it joins the parts with type: "text". The header line shows the author (participant, falling back to the role), the platform badge, the delivery status if any, and the local time from createdAt.

Provider-specific payloads never reach this component — normalization happens at ingestion. That's why one bubble works for every channel, including custom ones.

Props

proptypedescription
message *ChannelMessageCanonical channel_messages record: channel, direction, role, participant, status, text or parts, createdAt.
hideChannelbooleanHide the channel badge (e.g. in single-channel views).
classNamestringExtra classes for the bubble row.

Next

database…