Email Metadata Queries

Content: Email Metadata Queries

User Intent

"How do I query emails by sender, subject, labels, etc.?"

Operation

  • SDK Method: queryContents() with email-specific patterns

  • GraphQL: queryContents query

  • Entity Type: Content (type: EMAIL)

  • Common Use Cases: Find emails by sender, filter by labels, search by subject, date range queries

Email Metadata Structure

Emails have rich metadata captured in the email field:

interface EmailMetadata {
  identifier: string;              // Message-ID
  threadIdentifier: string;        // Thread ID
  subject: string;
  from: PersonReference[];
  to: PersonReference[];
  cc: PersonReference[];
  bcc: PersonReference[];
  labels: string[];                // Gmail labels
  sensitivity: MailSensitivity;
  priority: MailPriority;
  importance: MailImportance;
  attachmentCount: number;
  unsubscribeUrl: string;
  publicationName: string;
  publicationUrl: string;
}

TypeScript (Canonical)

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

const graphlit = new Graphlit();

// Query all emails
const allEmails = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email]
  }
});

// Search emails by keyword
const searchEmails = await graphlit.queryContents({
  search: "quarterly report",
  filter: {
    types: [ContentTypes.Email]
  }
});

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

// Emails from specific sender (keyword search)
const fromSender = await graphlit.queryContents({
  search: "[email protected]",
  searchType: SearchTypes.Keyword,
  filter: {
    types: [ContentTypes.Email]
  }
});

// Emails with attachments
const withAttachments = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email]
  }
});

// Filter client-side for attachments
const hasAttachments = withAttachments.contents.results.filter(
  email => email.email && email.email.attachmentCount > 0
);

console.log(`Found ${hasAttachments.length} emails with attachments`);

Query Patterns

1. Find by Sender

// Search by sender email
const fromKirk = await graphlit.queryContents({
  search: "[email protected]",
  searchType: SearchTypes.Keyword,
  filter: {
    types: [ContentTypes.Email]
  }
});

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

// Access sender details
fromKirk.contents.results.forEach(email => {
  if (email.email && email.email.from) {
    console.log(`From: ${email.email.from[0].name} <${email.email.from[0].email}>`);
    console.log(`Subject: ${email.email.subject}`);
  }
});

2. Find by Subject

// Search in subject
const subjectSearch = await graphlit.queryContents({
  search: "quarterly earnings report",
  filter: {
    types: [ContentTypes.Email]
  }
});

// Exact subject match
const exactSubject = await graphlit.queryContents({
  search: '"Q4 2024 Earnings"',
  searchType: SearchTypes.Keyword,
  filter: {
    types: [ContentTypes.Email]
  }
});

3. Find by Recipient

// Search by recipient
const toMaria = await graphlit.queryContents({
  search: "[email protected]",
  searchType: SearchTypes.Keyword,
  filter: {
    types: [ContentTypes.Email]
  }
});

// Check if specific person is in TO/CC/BCC
const allEmails = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email]
  }
});

const toRecipient = allEmails.contents.results.filter(email => {
  if (!email.email) return false;
  
  const allRecipients = [
    ...(email.email.to || []),
    ...(email.email.cc || []),
    ...(email.email.bcc || [])
  ];
  
  return allRecipients.some(r => r.email === "[email protected]");
});

console.log(`Found ${toRecipient.length} emails to [email protected]`);

4. Filter by Labels (Gmail)

// Get emails with specific label
const emails = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email]
  }
});

// Filter by Gmail label
const important = emails.contents.results.filter(email =>
  email.email?.labels?.includes('IMPORTANT')
);

const unread = emails.contents.results.filter(email =>
  email.email?.labels?.includes('UNREAD')
);

const starred = emails.contents.results.filter(email =>
  email.email?.labels?.includes('STARRED')
);

console.log(`Important: ${important.length}`);
console.log(`Unread: ${unread.length}`);
console.log(`Starred: ${starred.length}`);

5. Date Range Queries

// Emails from last month
const lastMonth = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email],
    createdInLast: 'P30D'
  }
});

// Emails in specific date range
const dateRange = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email],
    creationDateRange: {
      from: '2024-01-01T00:00:00Z',
      to: '2024-12-31T23:59:59Z'
    }
  }
});

// Q4 2024 emails
const q4 = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email],
    creationDateRange: {
      from: '2024-10-01',
      to: '2024-12-31'
    }
  }
});

6. Thread Queries

// Get all emails
const emails = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email]
  },
  limit: 100
});

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

emails.contents.results.forEach(email => {
  if (email.email?.threadIdentifier) {
    const threadId = email.email.threadIdentifier;
    if (!threads.has(threadId)) {
      threads.set(threadId, []);
    }
    threads.get(threadId)?.push(email);
  }
});

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

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

console.log('\nTop 5 longest threads:');
sortedThreads.slice(0, 5).forEach(([threadId, emails]) => {
  console.log(`Thread ${threadId}: ${emails.length} emails`);
  console.log(`  Subject: ${emails[0].email?.subject}`);
});

7. With Attachments

// Query all emails
const emails = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email]
  }
});

// Filter by attachment count
const withAttachments = emails.contents.results.filter(email =>
  email.email && email.email.attachmentCount > 0
);

const multipleAttachments = emails.contents.results.filter(email =>
  email.email && email.email.attachmentCount > 1
);

console.log(`With attachments: ${withAttachments.length}`);
console.log(`Multiple attachments: ${multipleAttachments.length}`);

// List emails with most attachments
const sorted = emails.contents.results
  .filter(email => email.email && email.email.attachmentCount > 0)
  .sort((a, b) => (b.email?.attachmentCount || 0) - (a.email?.attachmentCount || 0));

console.log('\nTop 5 emails by attachment count:');
sorted.slice(0, 5).forEach(email => {
  console.log(`${email.name}: ${email.email?.attachmentCount} attachments`);
});

8. Newsletter Detection

// Find newsletters
const emails = await graphlit.queryContents({
  filter: {
    types: [ContentTypes.Email]
  }
});

const newsletters = emails.contents.results.filter(email =>
  email.email?.publicationName || email.email?.unsubscribeUrl
);

console.log(`Found ${newsletters.length} newsletters`);

// Group by publication
const byPublication = new Map<string, number>();
newsletters.forEach(email => {
  const pub = email.email?.publicationName || 'Unknown';
  byPublication.set(pub, (byPublication.get(pub) || 0) + 1);
});

console.log('\nNewsletters by publication:');
Array.from(byPublication.entries())
  .sort((a, b) => b[1] - a[1])
  .forEach(([pub, count]) => {
    console.log(`  ${pub}: ${count}`);
  });

Sample Reference

  • Graphlit_2024_09_07_Locate_Google_Emails_by_Person.ipynb

  • Graphlit_2024_12_09_Locate_Microsoft_Emails_by_Organization.ipynb

Query emails

emails = await graphlit.queryContents( filter=ContentFilterInput( types=[ContentTypes.Email] ) )

By sender

from_kirk = await graphlit.queryContents( search="[email protected]", search_type=SearchTypes.Keyword, filter=ContentFilterInput( types=[ContentTypes.Email] ) )

Recent emails

recent = await graphlit.queryContents( filter=ContentFilterInput( types=[ContentTypes.Email], created_in_last='P7D' ) )

Access metadata

for email in emails.contents.results: if email.email: print(f"From: {email.email.from_[0].email}") print(f"Subject: {email.email.subject}")


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

var client = new Graphlit();

// Query emails
var emails = await graphlit.QueryContents(new ContentFilter
{
    Types = new[] { ContentTypes.Email }
});

// By sender
var fromKirk = await graphlit.QueryContents(new ContentFilter
{
    Search = "[email protected]",
    SearchType = SearchKeyword,
    Filter = new ContentCriteria
    {
        Types = new[] { ContentTypes.Email }
    }
});

// Recent emails
var recent = await graphlit.QueryContents(new ContentFilter
{
    Filter = new ContentCriteria
    {
        Types = new[] { ContentTypes.Email },
        CreatedInLast = "P7D"
    }
});

// Access metadata
foreach (var email in emails.Contents.Results)
{
    if (email.Email != null)
    {
        Console.WriteLine($"From: {email.Email.From[0].Email}");
        Console.WriteLine($"Subject: {email.Email.Subject}");
    }
}

Developer Hints

Email Addresses are Searchable

// Keyword search works for email addresses
const results = await graphlit.queryContents({
  search: "[email protected]",
  searchType: SearchTypes.Keyword,
  filter: { types: [ContentTypes.Email] }
});

Labels are Gmail-Specific

// Gmail uses labels
email.email?.labels  // ["INBOX", "IMPORTANT", "UNREAD"]

// Outlook uses folders (not in labels field)
// Check feed type to determine source

Thread Detection

// Emails with same threadIdentifier are part of same conversation
if (email.email?.threadIdentifier) {
  console.log(`Part of thread: ${email.email.threadIdentifier}`);
}

Common Issues & Solutions

Issue: Can't filter by specific label Solution: Query all emails, filter client-side

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

const important = all.contents.results.filter(
  e => e.email?.labels?.includes('IMPORTANT')
);

Issue: Want emails TO a specific person Solution: Search by email or filter client-side

// Keyword search
await graphlit.queryContents({
  search: "[email protected]",
  filter: { types: [ContentTypes.Email] }
});

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

const toMaria = all.contents.results.filter(e =>
  e.email?.to?.some(r => r.email === "[email protected]")
);

Issue: Need to count emails by sender Solution: Query and aggregate client-side

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

const bySender = new Map<string, number>();
emails.contents.results.forEach(email => {
  const sender = email.email?.from[0]?.email || 'unknown';
  bySender.set(sender, (bySender.get(sender) || 0) + 1);
});

Production Example

async function analyzeEmails() {
  console.log('\n=== EMAIL ANALYSIS ===\n');
  
  // Get all emails
  const emails = await graphlit.queryContents({
    filter: {
      types: [ContentTypes.Email],
      createdInLast: 'P30D'  // Last 30 days
    },
    limit: 500
  });
  
  console.log(`Total emails: ${emails.contents.results.length}`);
  
  // Analyze senders
  const senders = new Map<string, number>();
  emails.contents.results.forEach(email => {
    if (email.email?.from[0]) {
      const sender = email.email.from[0].email;
      senders.set(sender, (senders.get(sender) || 0) + 1);
    }
  });
  
  console.log(`\nTop 10 senders:`);
  Array.from(senders.entries())
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10)
    .forEach(([sender, count]) => {
      console.log(`  ${sender}: ${count} emails`);
    });
  
  // Attachment statistics
  const withAttachments = emails.contents.results.filter(
    e => e.email && e.email.attachmentCount > 0
  );
  console.log(`\nEmails with attachments: ${withAttachments.length} (${(withAttachments.length / emails.contents.results.length * 100).toFixed(1)}%)`);
  
  // Thread analysis
  const threads = new Map<string, any[]>();
  emails.contents.results.forEach(email => {
    if (email.email?.threadIdentifier) {
      const tid = email.email.threadIdentifier;
      if (!threads.has(tid)) threads.set(tid, []);
      threads.get(tid)?.push(email);
    }
  });
  
  console.log(`\nThreads: ${threads.size}`);
  const threadLengths = Array.from(threads.values()).map(t => t.length);
  const avgThreadLength = threadLengths.reduce((a, b) => a + b, 0) / threadLengths.length;
  console.log(`Average thread length: ${avgThreadLength.toFixed(1)} emails`);
  
  // Labels (Gmail)
  const allLabels = new Set<string>();
  emails.contents.results.forEach(email => {
    email.email?.labels?.forEach(label => allLabels.add(label));
  });
  console.log(`\nUnique labels: ${allLabels.size}`);
  console.log(`Labels: ${Array.from(allLabels).join(', ')}`);
  
  // Newsletters
  const newsletters = emails.contents.results.filter(
    e => e.email?.publicationName
  );
  console.log(`\nNewsletters: ${newsletters.length}`);
}

await analyzeEmails();

Last updated

Was this helpful?