Conversations#
Conversations in Outeract are logical groupings of messages between participants. They emerge naturally from the event graph through edges, providing context for message threads and enabling conversation-level analytics.
Overview#
Unlike traditional chat systems with explicit conversation objects, Outeract derives conversations from message relationships:
flowchart TB
subgraph Conv["Conversation View<br/>User: Alice (+14155551234) • Platform: WhatsApp"]
M1["[10:30] Alice: Hi, I need help with my order"]
M2["[10:31] Bot: Hello! I'd be happy to help..."]
M3["[10:32] Alice: Order #12345"]
M4["[10:33] Bot: I found your order. It's shipping..."]
M5["[10:35] Alice: Thanks!"]
M1 --> M2 --> M3 --> M4 --> M5
endConversation Model#
Conversations are virtual aggregations built from:
| Component | Role |
|---|---|
| Events | Individual messages (inbound/outbound) |
| Edges | sent_by and sent_to relationships |
| Platform Users | Conversation participants |
| Platform Connection | The channel (WhatsApp, Slack, etc.) |
Identifying Conversations#
By Platform User#
The most common pattern - get all messages to/from a specific user:
query ConversationWithUser($platformUserId: UUID!) {
events(
filters: {
eventType: { contains: "message" }
or: [
{ hasEdge: { edgeType: "sent_by", targetNodeId: { equals: $platformUserId } } }
{ hasEdge: { edgeType: "sent_to", targetNodeId: { equals: $platformUserId } } }
]
}
orderBy: { field: CREATED_AT, direction: ASC }
) {
edges {
node {
id
eventType
payload
createdAt
edges {
edgeType
targetPlatformUser {
id
externalId
}
}
}
}
}
}By User (Cross-Platform)#
Get conversations across all platforms for a unified user:
query CrossPlatformConversation($userId: UUID!) {
user(id: $userId) {
id
name
platformUsers {
id
externalId
platformConnection {
platformName
}
# Get messages for each platform identity
sentMessages: events(
filters: { hasEdge: { edgeType: "sent_by" } }
first: 50
) {
edges {
node {
id
payload
createdAt
}
}
}
}
}
}By Time Window#
Get recent conversations with activity:
query RecentConversations($since: DateTime!) {
events(
filters: {
eventType: { equals: "message.inbound" }
createdAt: { gte: $since }
}
orderBy: { field: CREATED_AT, direction: DESC }
) {
edges {
node {
id
payload
createdAt
edges(edgeType: "sent_by") {
targetPlatformUser {
id
externalId
name
user {
id
name
}
}
}
}
}
}
}Conversation Patterns#
1:1 Conversations#
Direct messages between your system and a user:
flowchart LR
PU["Platform User<br/>(Customer)"] <-->|messages| SU["System User<br/>(Your Bot)"]Query pattern:
query DirectConversation($platformUserId: UUID!, $systemUserId: UUID!) {
events(
filters: {
eventType: { contains: "message" }
and: [
{
or: [
{ hasEdge: { edgeType: "sent_by", targetNodeId: { equals: $platformUserId } } }
{ hasEdge: { edgeType: "sent_by", targetNodeId: { equals: $systemUserId } } }
]
}
{
or: [
{ hasEdge: { edgeType: "sent_to", targetNodeId: { equals: $platformUserId } } }
{ hasEdge: { edgeType: "sent_to", targetNodeId: { equals: $systemUserId } } }
]
}
]
}
orderBy: { field: CREATED_AT, direction: ASC }
) {
edges {
node {
id
eventType
payload
createdAt
}
}
}
}Group Conversations#
Messages with multiple recipients (e.g., Slack channels):
flowchart TB
GM["Group Message<br/>(Event)"]
GM --> A["sent_to<br/>User A"]
GM --> B["sent_to<br/>User B"]
GM --> C["sent_to<br/>User C"]Threaded Conversations#
Reply chains using reply_to edges:
query ThreadedConversation($rootMessageId: UUID!) {
event(id: $rootMessageId) {
id
payload
# Get all replies
replies: incomingEdges(edgeType: "reply_to") {
sourceEvent {
id
payload
createdAt
edges(edgeType: "sent_by") {
targetPlatformUser {
name
}
}
# Nested replies
replies: incomingEdges(edgeType: "reply_to") {
sourceEvent {
id
payload
}
}
}
}
}
}Building a Conversation List#
Get Active Conversations#
List users with recent message activity:
query ActiveConversations($since: DateTime!) {
platformUsers(
filters: {
hasEvents: {
eventType: { contains: "message" }
createdAt: { gte: $since }
}
}
first: 50
) {
edges {
node {
id
externalId
name
platformConnection {
platformName
}
# Latest message
latestMessage: events(
filters: { eventType: { contains: "message" } }
orderBy: { field: CREATED_AT, direction: DESC }
first: 1
) {
edges {
node {
id
eventType
payload
createdAt
}
}
}
# Unread count (messages since last outbound)
messageCount: eventsCount(
filters: { eventType: { equals: "message.inbound" } }
)
}
}
}
}Conversation Summary#
Get conversation metadata:
query ConversationSummary($platformUserId: UUID!) {
platformUser(id: $platformUserId) {
id
externalId
name
# First message
firstMessage: events(
filters: { eventType: { contains: "message" } }
orderBy: { field: CREATED_AT, direction: ASC }
first: 1
) {
edges {
node {
createdAt
}
}
}
# Latest message
latestMessage: events(
filters: { eventType: { contains: "message" } }
orderBy: { field: CREATED_AT, direction: DESC }
first: 1
) {
edges {
node {
createdAt
payload
}
}
}
# Message counts
inboundCount: eventsCount(
filters: { eventType: { equals: "message.inbound" } }
)
outboundCount: eventsCount(
filters: { eventType: { equals: "message.outbound" } }
)
}
}Message Context#
Get Surrounding Messages#
Fetch context around a specific message:
query MessageContext($messageId: UUID!, $platformUserId: UUID!) {
# Messages before
before: events(
filters: {
eventType: { contains: "message" }
hasEdge: { targetNodeId: { equals: $platformUserId } }
id: { lt: $messageId }
}
orderBy: { field: CREATED_AT, direction: DESC }
first: 5
) {
edges {
node {
id
payload
createdAt
}
}
}
# The message itself
message: event(id: $messageId) {
id
payload
createdAt
}
# Messages after
after: events(
filters: {
eventType: { contains: "message" }
hasEdge: { targetNodeId: { equals: $platformUserId } }
id: { gt: $messageId }
}
orderBy: { field: CREATED_AT, direction: ASC }
first: 5
) {
edges {
node {
id
payload
createdAt
}
}
}
}Conversation State#
Track conversation state using custom events:
# Mark conversation as handled
mutation MarkHandled($platformUserId: UUID!) {
createEvent(
eventType: "custom.conversation.handled"
payload: {
platform_user_id: $platformUserId
handled_by: "agent_123"
handled_at: "2024-01-15T10:30:00Z"
}
) {
id
}
}
# Query conversation state
query ConversationState($platformUserId: UUID!) {
events(
filters: {
eventType: { equals: "custom.conversation.handled" }
payload: { path: "platform_user_id", equals: $platformUserId }
}
orderBy: { field: CREATED_AT, direction: DESC }
first: 1
) {
edges {
node {
payload
createdAt
}
}
}
}Real-Time Updates#
Subscribe to conversation updates via webhooks:
mutation SubscribeToMessages {
createWebhookSubscription(
name: "Conversation Updates"
targetUrl: "https://myapp.com/webhooks/messages"
eventPatterns: ["message.inbound", "message.outbound"]
secret: "webhook-secret"
) {
id
}
}Your webhook receives:
{
"event_id": "evt_abc123",
"event_type": "message.inbound",
"payload": {
"message": { "text": "Hello!" }
},
"edges": {
"sent_by": {
"platform_user_id": "pu_xyz789",
"external_id": "+14155551234"
}
},
"created_at": "2024-01-15T10:30:00Z"
}Pagination#
For long conversations, use cursor-based pagination:
query PaginatedConversation($platformUserId: UUID!, $cursor: String) {
events(
filters: {
eventType: { contains: "message" }
hasEdge: { targetNodeId: { equals: $platformUserId } }
}
orderBy: { field: CREATED_AT, direction: DESC }
first: 20
after: $cursor
) {
edges {
node {
id
payload
createdAt
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}Best Practices#
1. Index by Platform User#
Structure queries around platform users for efficient conversation retrieval.
2. Use Pagination#
Always paginate message queries - conversations can have thousands of messages.
3. Cache Conversation Metadata#
Cache summaries (message count, last activity) and update incrementally.
4. Handle Multiple Platforms#
Remember that one User can have conversations across multiple Platform Users.
5. Track State Externally#
Store conversation state (open/closed, assigned agent) in your application.
6. Use Webhooks for Real-Time#
Don’t poll for new messages - subscribe to webhooks for real-time updates.