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| ConvHow Conversations Work#
Auto-Creation#
When a message arrives via a platform webhook, Outeract:
- Resolves the sender and recipient to Users (via their platform identities)
- Looks for an existing conversation with exactly that user set
- If exactly one match exists, reuses it
- If zero or multiple matches exist, creates a new one
- Links the message to the conversation with an
in_conversationedge
This happens automatically in the message processing pipeline - you don’t need to create conversations manually.
Graph Structure#
Conversations use two edge types:
| Edge Type | Direction | Description |
|---|---|---|
participant | Conversation (event) → User | Links a conversation to each of its participants |
in_conversation | Message (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
modefield (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"
}| Field | Type | Description |
|---|---|---|
type | string | Always "conversation" |
identities | string[] | UserIdentity UUIDs of the initial participants |
title | string | Auto-generated from participant names, or custom |
description | string | How the conversation was created |
created_via_platform | string | Platform that triggered creation (e.g. "whatsapp") |
mode | string | Conversation 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
first | Int | 25 | Number of conversations to return |
after | String | null | Cursor for pagination |
search | String | null | Filter 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
userId | UUID | required | User ID to get conversations for |
limit | Int | 50 | Maximum 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
conversationIds | [UUID!]! | required | List of conversation IDs to delete |
deleteMessages | Boolean | false | If true, also delete all message events in the conversations |
Returns: Int - the number of conversations deleted.
When deleteMessages is true, the mutation deletes:
- All message events linked to the conversation via
in_conversationedges - All edges and webhook deliveries associated with those messages
- The conversation event itself and its edges
ConversationSummary Type#
Both conversations and userConversations return ConversationSummary objects:
| Field | Type | Description |
|---|---|---|
id | String | Conversation event UUID |
title | String | Conversation title (auto-generated or custom) |
createdAt | String | ISO 8601 timestamp of creation |
updatedAt | String | ISO 8601 timestamp of last activity |
messageCount | Int | Total number of messages in the conversation |
participants | [ConversationParticipant] | List of participants |
ConversationParticipant Type#
| Field | Type | Description |
|---|---|---|
userId | String | User UUID |
userName | String | User 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.