Conversations#

Conversations in Outeract are first-class objects that group messages between participants. They are stored as events, auto-created when messages arrive, and queryable through dedicated GraphQL queries.

Overview#

When a message is sent or received, Outeract automatically finds or creates a conversation matching the sender and recipient. Conversations are stored as conversation.created events with graph edges linking them to their participants and messages.

flowchart TB
    Conv["conversation.created<br/>(Event)"]
    Conv -->|participant| Alice["User: Alice"]
    Conv -->|participant| Bot["User: Support Bot"]
    M1["message.inbound"] -->|in_conversation| Conv
    M2["message.outbound"] -->|in_conversation| Conv
    M3["message.inbound"] -->|in_conversation| Conv

How Conversations Work#

Auto-Creation#

When a message arrives via a platform webhook, Outeract:

  1. Resolves the sender and recipient to Users (via their platform identities)
  2. Looks for an existing conversation with exactly that user set
  3. If exactly one match exists, reuses it
  4. If zero or multiple matches exist, creates a new one
  5. Links the message to the conversation with an in_conversation edge

This happens automatically in the message processing pipeline - you don’t need to create conversations manually.

Graph Structure#

Conversations use two edge types:

Edge TypeDirectionDescription
participantConversation (event) → UserLinks a conversation to each of its participants
in_conversationMessage (event) → Conversation (event)Links a message to its conversation

The diagram above shows both edge types: participant edges fan out from the conversation to each user, and in_conversation edges link every message back to it.

Conversation Matching#

Conversations are matched by their exact participant set. Two users always map to the same conversation (assuming the same mode). This means:

  • The same pair of users shares one conversation regardless of which platform they message on
  • Group conversations match on the full set of participants
  • A conversation’s mode field (default: "private") is included in matching, so the same user pair can have separate conversations with different modes

Conversation Payload#

Conversation events have a conversation.created event type with this payload structure:

{
  "type": "conversation",
  "identities": ["identity-uuid-1", "identity-uuid-2"],
  "title": "Alice and Bob",
  "description": "Initially created via WhatsApp",
  "created_via_platform": "whatsapp",
  "mode": "private"
}
FieldTypeDescription
typestringAlways "conversation"
identitiesstring[]UserIdentity UUIDs of the initial participants
titlestringAuto-generated from participant names, or custom
descriptionstringHow the conversation was created
created_via_platformstringPlatform that triggered creation (e.g. "whatsapp")
modestringConversation mode (default: "private")

The title is auto-generated from participant names using natural language formatting:

  • 1 participant: "Alice"
  • 2 participants: "Alice and Bob"
  • 3+ participants: "Alice, Bob, and Charlie"

GraphQL Queries#

List All Conversations#

Use conversations to list all conversations for the application with Relay-style pagination and optional search:

query {
  conversations(first: 25) {
    edges {
      node {
        id
        title
        createdAt
        updatedAt
        messageCount
        participants {
          userId
          userName
        }
      }
      cursor
    }
    pageInfo {
      hasNextPage
      endCursor
      count
    }
  }
}

Parameters:

ParameterTypeDefaultDescription
firstInt25Number of conversations to return
afterStringnullCursor for pagination
searchStringnullFilter by title or participant name

Conversations are returned sorted by most recent activity (updatedAt descending).

Get User’s Conversations#

Use userConversations to get all conversations a specific user participates in:

query {
  userConversations(userId: "user-uuid-here", limit: 50) {
    id
    title
    createdAt
    updatedAt
    messageCount
    participants {
      userId
      userName
    }
  }
}

Parameters:

ParameterTypeDefaultDescription
userIdUUIDrequiredUser ID to get conversations for
limitInt50Maximum conversations to return

Get Messages in a Conversation#

Use the events query with a relatedTo filter to get messages within a conversation:

query {
  events(
    last: 50
    relatedTo: { eventIds: ["conversation-uuid-here"] }
    eventTypes: ["message.inbound", "message.outbound"]
  ) {
    edges {
      node {
        id
        eventTypeName
        payload
        createdAt
        edges {
          edgeType
          targetNodeType
          targetNodeId
        }
      }
      cursor
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
  }
}

GraphQL Mutation#

Delete Conversations#

Delete one or more conversations, optionally including all their messages:

mutation {
  deleteConversations(
    conversationIds: ["conv-uuid-1", "conv-uuid-2"]
    deleteMessages: true
  )
}

Parameters:

ParameterTypeDefaultDescription
conversationIds[UUID!]!requiredList of conversation IDs to delete
deleteMessagesBooleanfalseIf true, also delete all message events in the conversations

Returns: Int - the number of conversations deleted.

When deleteMessages is true, the mutation deletes:

  1. All message events linked to the conversation via in_conversation edges
  2. All edges and webhook deliveries associated with those messages
  3. The conversation event itself and its edges

ConversationSummary Type#

Both conversations and userConversations return ConversationSummary objects:

FieldTypeDescription
idStringConversation event UUID
titleStringConversation title (auto-generated or custom)
createdAtStringISO 8601 timestamp of creation
updatedAtStringISO 8601 timestamp of last activity
messageCountIntTotal number of messages in the conversation
participants[ConversationParticipant]List of participants

ConversationParticipant Type#

FieldTypeDescription
userIdStringUser UUID
userNameStringUser display name (may be null)

Real-Time Updates#

Subscribe to conversation activity via webhook subscriptions. In the console under Settings → Webhooks, create a subscription pointing at your endpoint (e.g. https://myapp.com/webhooks/messages) matching the message.inbound and message.outbound event types.

Your webhook receives events with their edges, allowing you to identify the conversation and participants for each message.

Best Practices#

Use Dedicated Queries#

Use conversations and userConversations instead of manually querying events and edges. These queries handle the participant resolution, message counting, and sorting efficiently with batch queries.

Paginate Results#

Always paginate conversation lists and message queries. Conversations can accumulate thousands of messages over time.

Track Activity via updatedAt#

Conversations have their updatedAt timestamp updated whenever a new message is linked. Use this for sorting by recent activity.

Handle Multiple Platforms#

A single conversation can span multiple platforms. Users may message from WhatsApp, then later from Instagram - both map to the same conversation if the identities belong to the same users.

Use Webhooks for Real-Time#

Don’t poll for new messages. Subscribe to webhooks for message.inbound and message.outbound event patterns.

  • Events - Conversations and messages are both events
  • Edges - How messages and participants connect to conversations
  • Users - The participants in conversations