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:

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)

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({
  filter: {
    types: [ContentTypes.Message]
  }
});

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

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

// Messages from specific feed (e.g., Slack workspace)
const slackMessages = await graphlit.queryContents({
  filter: {
    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

// Get messages from all channels
const messages = await graphlit.queryContents({
  filter: {
    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

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

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

const kirkMessages = messages.contents.results.filter(msg =>
  msg.message?.author?.email === "[email protected]"
);

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

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

// Find messages mentioning specific user
const mentioningKirk = messages.contents.results.filter(msg =>
  msg.message?.mentions?.some(m => m.email === "[email protected]")
);

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

// Get all messages
const messages = await graphlit.queryContents({
  filter: {
    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`);
// Get messages
const messages = await graphlit.queryContents({
  filter: {
    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

// Get messages
const messages = await graphlit.queryContents({
  filter: {
    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

// Get messages
const messages = await graphlit.queryContents({
  filter: {
    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

// Get messages
const messages = await graphlit.queryContents({
  filter: {
    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

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

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

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

Mentions Array

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

Thread Detection

// 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

const all = await graphlit.queryContents({
  filter: { 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

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

const kirkToMaria = messages.contents.results.filter(msg =>
  msg.message?.author?.email === '[email protected]' &&
  msg.message?.mentions?.some(m => m.email === '[email protected]')
);

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

const messages = await graphlit.queryContents({
  filter: { 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

async function analyzeTeamCommunication() {
  console.log('\n=== TEAM COMMUNICATION ANALYSIS ===\n');
  
  // Get recent messages
  const messages = await graphlit.queryContents({
    filter: {
      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();

Last updated

Was this helpful?