# Message Metadata Queries

## Content: Message Metadata Queries

### User Intent

"How do I query Slack/Teams/Discord messages by channel, author, mentions, etc.?"

### Operation

* **SDK Method**: `queryContents()` with message-specific patterns
* **GraphQL**: `queryContents` query
* **Entity Type**: Content (type: MESSAGE)
* **Common Use Cases**: Find messages by channel, author queries, mentions detection, link analysis

### Message Metadata Structure

Messages (from Slack, Teams, Discord) have metadata in the `message` field:

```typescript
interface MessageMetadata {
  identifier: string;              // Message ID
  conversationIdentifier: string;  // Thread/conversation ID
  channelIdentifier: string;       // Channel ID
  channelName: string;             // Channel name
  author: PersonReference;         // Message author
  mentions: PersonReference[];     // @mentioned users
  attachmentCount: number;
  links: string[];                 // URLs in message
}
```

### TypeScript (Canonical)

```typescript
import { Graphlit } from 'graphlit-client';
import { ContentTypes, SearchTypes } from 'graphlit-client/dist/generated/graphql-types';

const graphlit = new Graphlit();

// Query all messages
const allMessages = await graphlit.queryContents({
  
    types: [ContentTypes.Message]
  });

// Search messages by keyword
const searchMessages = await graphlit.queryContents({
  search: "product roadmap",
  
    types: [ContentTypes.Message]
  });

// Recent messages
const recentMessages = await graphlit.queryContents({
  
    types: [ContentTypes.Message],
    createdInLast: 'P7D'  // Last 7 days
  });

// Messages from specific feed (e.g., Slack workspace)
const slackMessages = await graphlit.queryContents({
  
    types: [ContentTypes.Message],
    feeds: [{ id: 'slack-feed-id' }]
  });

console.log(`Found ${slackMessages.contents.results.length} messages`);

// Access message metadata
slackMessages.contents.results.forEach(msg => {
  if (msg.message) {
    console.log(`Channel: ${msg.message.channelName}`);
    console.log(`Author: ${msg.message.author?.name}`);
    console.log(`Message: ${msg.markdown?.substring(0, 100)}...`);
  }
});
```

### Query Patterns

#### 1. Filter by Channel

```typescript
// Get messages from all channels
const messages = await graphlit.queryContents({
  
    types: [ContentTypes.Message]
  
  limit: 500
});

// Filter by channel name
const engineering = messages.contents.results.filter(msg =>
  msg.message?.channelName === 'engineering'
);

const product = messages.contents.results.filter(msg =>
  msg.message?.channelName === 'product'
);

console.log(`Engineering messages: ${engineering.length}`);
console.log(`Product messages: ${product.length}`);

// List all channels
const channels = new Set(
  messages.contents.results
    .map(m => m.message?.channelName)
    .filter(Boolean)
);

console.log(`Channels: ${Array.from(channels).join(', ')}`);
```

#### 2. Find by Author

```typescript
// Search by author name
const byAuthor = await graphlit.queryContents({
  search: "Kirk Marple",
  searchType: SearchTypes.Keyword,
  
    types: [ContentTypes.Message]
  });

// Filter by author email
const messages = await graphlit.queryContents({
  
    types: [ContentTypes.Message]
  });

const kirkMessages = messages.contents.results.filter(msg =>
  msg.message?.author?.email === "kirk@graphlit.com"
);

console.log(`Kirk's messages: ${kirkMessages.length}`);

// Count messages by author
const byAuthorCount = new Map<string, number>();
messages.contents.results.forEach(msg => {
  if (msg.message?.author) {
    const author = msg.message.author.name || msg.message.author.email;
    byAuthorCount.set(author, (byAuthorCount.get(author) || 0) + 1);
  }
});

console.log('\nTop 10 authors:');
Array.from(byAuthorCount.entries())
  .sort((a, b) => b[1] - a[1])
  .slice(0, 10)
  .forEach(([author, count]) => {
    console.log(`  ${author}: ${count} messages`);
  });
```

#### 3. Find Mentions

```typescript
// Get all messages
const messages = await graphlit.queryContents({
  
    types: [ContentTypes.Message]
  });

// Find messages mentioning specific user
const mentioningKirk = messages.contents.results.filter(msg =>
  msg.message?.mentions?.some(m => m.email === "kirk@graphlit.com")
);

console.log(`Messages mentioning Kirk: ${mentioningKirk.length}`);

// Find messages with any mentions
const withMentions = messages.contents.results.filter(msg =>
  msg.message?.mentions && msg.message.mentions.length > 0
);

console.log(`Messages with mentions: ${withMentions.length}`);

// Count mentions per person
const mentionCounts = new Map<string, number>();
messages.contents.results.forEach(msg => {
  msg.message?.mentions?.forEach(mention => {
    const name = mention.name || mention.email;
    mentionCounts.set(name, (mentionCounts.get(name) || 0) + 1);
  });
});

console.log('\nMost mentioned people:');
Array.from(mentionCounts.entries())
  .sort((a, b) => b[1] - a[1])
  .slice(0, 10)
  .forEach(([person, count]) => {
    console.log(`  ${person}: ${count} mentions`);
  });
```

#### 4. Thread/Conversation Queries

```typescript
// Get all messages
const messages = await graphlit.queryContents({
  
    types: [ContentTypes.Message]
  });

// Group by conversation/thread
const conversations = new Map<string, any[]>();

messages.contents.results.forEach(msg => {
  if (msg.message?.conversationIdentifier) {
    const convId = msg.message.conversationIdentifier;
    if (!conversations.has(convId)) {
      conversations.set(convId, []);
    }
    conversations.get(convId)?.push(msg);
  }
});

console.log(`Found ${conversations.size} conversations`);

// Find longest threads
const sortedConversations = Array.from(conversations.entries())
  .sort((a, b) => b[1].length - a[1].length);

console.log('\nTop 5 longest threads:');
sortedConversations.slice(0, 5).forEach(([convId, messages]) => {
  const channel = messages[0].message?.channelName;
  const firstMessage = messages[0].markdown?.substring(0, 50);
  console.log(`${messages.length} messages in #${channel}: "${firstMessage}..."`);
});

// Find replies (where identifier !== conversationIdentifier)
const replies = messages.contents.results.filter(msg =>
  msg.message?.identifier !== msg.message?.conversationIdentifier
);

console.log(`\nReplies: ${replies.length} out of ${messages.contents.results.length} messages`);
```

#### 5. Messages with Links

```typescript
// Get messages
const messages = await graphlit.queryContents({
  
    types: [ContentTypes.Message]
  });

// Filter messages with links
const withLinks = messages.contents.results.filter(msg =>
  msg.message?.links && msg.message.links.length > 0
);

console.log(`Messages with links: ${withLinks.length}`);

// Extract all shared links
const allLinks = new Set<string>();
messages.contents.results.forEach(msg => {
  msg.message?.links?.forEach(link => allLinks.add(link));
});

console.log(`Unique links shared: ${allLinks.size}`);

// Most shared domains
const domains = new Map<string, number>();
allLinks.forEach(link => {
  try {
    const url = new URL(link);
    domains.set(url.hostname, (domains.get(url.hostname) || 0) + 1);
  } catch (e) {
    // Invalid URL
  }
});

console.log('\nMost shared domains:');
Array.from(domains.entries())
  .sort((a, b) => b[1] - a[1])
  .slice(0, 10)
  .forEach(([domain, count]) => {
    console.log(`  ${domain}: ${count} links`);
  });
```

#### 6. Messages with Attachments

```typescript
// Get messages
const messages = await graphlit.queryContents({
  
    types: [ContentTypes.Message]
  });

// Filter by attachments
const withAttachments = messages.contents.results.filter(msg =>
  msg.message && msg.message.attachmentCount > 0
);

console.log(`Messages with attachments: ${withAttachments.length}`);

// Distribution
const attachmentDist = new Map<number, number>();
withAttachments.forEach(msg => {
  const count = msg.message?.attachmentCount || 0;
  attachmentDist.set(count, (attachmentDist.get(count) || 0) + 1);
});

console.log('\nAttachment distribution:');
Array.from(attachmentDist.entries())
  .sort((a, b) => a[0] - b[0])
  .forEach(([count, messages]) => {
    console.log(`  ${count} attachment(s): ${messages} messages`);
  });
```

#### 7. Channel Activity Analysis

```typescript
// Get messages
const messages = await graphlit.queryContents({
  
    types: [ContentTypes.Message],
    createdInLast: 'P30D'  // Last 30 days
  
  limit: 1000
});

// Messages per channel
const byChannel = new Map<string, number>();
messages.contents.results.forEach(msg => {
  const channel = msg.message?.channelName || 'unknown';
  byChannel.set(channel, (byChannel.get(channel) || 0) + 1);
});

console.log('Channel activity (last 30 days):');
Array.from(byChannel.entries())
  .sort((a, b) => b[1] - a[1])
  .forEach(([channel, count]) => {
    console.log(`  #${channel}: ${count} messages`);
  });

// Active hours
const byHour = new Array(24).fill(0);
messages.contents.results.forEach(msg => {
  const hour = new Date(msg.creationDate).getHours();
  byHour[hour]++;
});

console.log('\nMost active hours:');
byHour
  .map((count, hour) => ({ hour, count }))
  .sort((a, b) => b.count - a.count)
  .slice(0, 5)
  .forEach(({ hour, count }) => {
    console.log(`  ${hour}:00 - ${count} messages`);
  });
```

#### 8. Collaboration Patterns

```typescript
// Get messages
const messages = await graphlit.queryContents({
  
    types: [ContentTypes.Message]
  });

// Build interaction matrix (who mentions whom)
const interactions = new Map<string, Map<string, number>>();

messages.contents.results.forEach(msg => {
  const author = msg.message?.author?.email;
  if (!author) return;
  
  msg.message?.mentions?.forEach(mention => {
    if (!interactions.has(author)) {
      interactions.set(author, new Map());
    }
    const authorInteractions = interactions.get(author)!;
    const mentionEmail = mention.email;
    authorInteractions.set(mentionEmail, (authorInteractions.get(mentionEmail) || 0) + 1);
  });
});

// Find strongest collaborations
console.log('Strongest collaborations:');
const collaborations: Array<{ from: string; to: string; count: number }> = [];

interactions.forEach((targets, source) => {
  targets.forEach((count, target) => {
    collaborations.push({ from: source, to: target, count });
  });
});

collaborations
  .sort((a, b) => b.count - a.count)
  .slice(0, 10)
  .forEach(({ from, to, count }) => {
    console.log(`  ${from} → ${to}: ${count} mentions`);
  });
```

## Query messages

messages = await graphlit.queryContents( filter=ContentFilterInput( types=\[ContentTypes.Message] ) )

## From specific feed

slack\_messages = await graphlit.queryContents( filter=ContentFilterInput( types=\[ContentTypes.Message], feeds=\[EntityReferenceInput(id='slack-feed-id')] ) )

## Access metadata

for msg in messages.contents.results: if msg.message: print(f"Channel: {msg.message.channel\_name}") print(f"Author: {msg.message.author.name}") if msg.message.mentions: print(f"Mentions: {len(msg.message.mentions)}")

````

**C#**:
```csharp
using Graphlit;

var client = new Graphlit();

// Query messages
var messages = await graphlit.QueryContents(new ContentFilter
{
    Filter = new ContentCriteria
    {
        Types = new[] { ContentTypes.Message }
    }
});

// From specific feed
var slackMessages = await graphlit.QueryContents(new ContentFilter
{
    Filter = new ContentCriteria
    {
        Types = new[] { ContentTypes.Message },
        Feeds = new[] { new EntityReference { Id = "slack-feed-id" } }
    }
});

// Access metadata
foreach (var msg in messages.Contents.Results)
{
    if (msg.Message != null)
    {
        Console.WriteLine($"Channel: {msg.Message.ChannelName}");
        Console.WriteLine($"Author: {msg.Message.Author.Name}");
        if (msg.Message.Mentions != null)
        {
            Console.WriteLine($"Mentions: {msg.Message.Mentions.Length}");
        }
    }
}
````

### Developer Hints

#### Channel Names are Searchable

```typescript
// Search for channel name
const results = await graphlit.queryContents({
  search: "engineering",
   types: [ContentTypes.Message] });

// Or filter after query
const all = await graphlit.queryContents({
   types: [ContentTypes.Message] });

const engineering = all.contents.results.filter(
  m => m.message?.channelName === 'engineering'
);
```

#### Mentions Array

```typescript
// Mentions is array of PersonReference
message.mentions.forEach(mention => {
  console.log(`@${mention.name} (${mention.email})`);
});
```

#### Thread Detection

```typescript
// Message is thread reply if:
if (message.identifier !== message.conversationIdentifier) {
  console.log('This is a reply in a thread');
}
```

### Common Issues & Solutions

**Issue**: Can't filter by specific channel in query **Solution**: Query all messages, filter client-side

```typescript
const all = await graphlit.queryContents({
   types: [ContentTypes.Message] });

const engineering = all.contents.results.filter(
  m => m.message?.channelName === 'engineering'
);
```

**Issue**: Want to find all messages from one user to another **Solution**: Filter by author and mentions

```typescript
const messages = await graphlit.queryContents({
   types: [ContentTypes.Message] });

const kirkToMaria = messages.contents.results.filter(msg =>
  msg.message?.author?.email === 'kirk@graphlit.com' &&
  msg.message?.mentions?.some(m => m.email === 'maria@example.com')
);
```

**Issue**: Need to count messages per channel **Solution**: Query and aggregate

```typescript
const messages = await graphlit.queryContents({
   types: [ContentTypes.Message] });

const byChannel = new Map<string, number>();
messages.contents.results.forEach(msg => {
  const channel = msg.message?.channelName || 'unknown';
  byChannel.set(channel, (byChannel.get(channel) || 0) + 1);
});
```

### Production Example

```typescript
async function analyzeTeamCommunication() {
  console.log('\n=== TEAM COMMUNICATION ANALYSIS ===\n');
  
  // Get recent messages
  const messages = await graphlit.queryContents({
    
      types: [ContentTypes.Message],
      createdInLast: 'P30D'
    
    limit: 1000
  });
  
  console.log(`Total messages (last 30 days): ${messages.contents.results.length}`);
  
  // Channel breakdown
  const channels = new Map<string, number>();
  messages.contents.results.forEach(msg => {
    const ch = msg.message?.channelName || 'unknown';
    channels.set(ch, (channels.get(ch) || 0) + 1);
  });
  
  console.log(`\nActive channels: ${channels.size}`);
  console.log('Top 10 channels:');
  Array.from(channels.entries())
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10)
    .forEach(([channel, count]) => {
      console.log(`  #${channel}: ${count} messages`);
    });
  
  // Author activity
  const authors = new Map<string, number>();
  messages.contents.results.forEach(msg => {
    const author = msg.message?.author?.name || 'unknown';
    authors.set(author, (authors.get(author) || 0) + 1);
  });
  
  console.log(`\nActive authors: ${authors.size}`);
  console.log('Top 10 authors:');
  Array.from(authors.entries())
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10)
    .forEach(([author, count]) => {
      console.log(`  ${author}: ${count} messages`);
    });
  
  // Mention statistics
  const mentioned = new Map<string, number>();
  let totalMentions = 0;
  
  messages.contents.results.forEach(msg => {
    msg.message?.mentions?.forEach(mention => {
      const name = mention.name || mention.email;
      mentioned.set(name, (mentioned.get(name) || 0) + 1);
      totalMentions++;
    });
  });
  
  console.log(`\nTotal mentions: ${totalMentions}`);
  console.log('Most mentioned:');
  Array.from(mentioned.entries())
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10)
    .forEach(([person, count]) => {
      console.log(`  ${person}: ${count} mentions`);
    });
  
  // Collaboration score
  const collaborations = new Map<string, Set<string>>();
  messages.contents.results.forEach(msg => {
    const author = msg.message?.author?.email;
    if (author) {
      if (!collaborations.has(author)) {
        collaborations.set(author, new Set());
      }
      msg.message?.mentions?.forEach(m => {
        collaborations.get(author)!.add(m.email);
      });
    }
  });
  
  console.log(`\n Collaboration Matrix:`);
  Array.from(collaborations.entries())
    .sort((a, b) => b[1].size - a[1].size)
    .slice(0, 5)
    .forEach(([author, collaborators]) => {
      console.log(`  ${author}: works with ${collaborators.size} people`);
    });
}

await analyzeTeamCommunication();
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.graphlit.dev/api-guides/use-cases/content/content-message-metadata-queries.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
