Searching Messages
EmailEngine provides powerful search capabilities to find messages across your email accounts. Search queries use IMAP SEARCH syntax under the hood, making them compatible with virtually any email server.
Why Use Search?
Performance
- Server-side filtering reduces data transfer
- Faster than fetching all messages and filtering locally
- Scales to mailboxes with thousands of messages
Flexibility
- Combine multiple criteria
- Search by date ranges, flags, content, headers
- Use complex boolean logic
Efficiency
- Returns only matching messages
- Reduces API calls and processing time
- Lower memory usage
Basic Search
Search by Subject
Find messages with specific subject text:
curl -X POST "https://your-emailengine.com/v1/account/example/search?path=INBOX" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"search": {
"subject": "meeting"
}
}'
JavaScript:
async function searchBySubject(accountId, folderPath, subjectText) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
subject: subjectText
}
})
}
);
return await response.json();
}
const results = await searchBySubject('example', 'INBOX', 'meeting');
console.log(`Found ${results.messages.length} messages`);
Search by Sender
Find messages from a specific sender:
async function searchBySender(accountId, folderPath, email) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
from: email
}
})
}
);
return await response.json();
}
// Find all emails from john@example.com
const results = await searchBySender('example', 'INBOX', 'john@example.com');
Search by Date
Find messages from a specific date range:
async function searchByDateRange(accountId, folderPath, since, before) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
since: since, // YYYY-MM-DD format
before: before
}
})
}
);
return await response.json();
}
// Find messages from last week
const today = new Date();
const weekAgo = new Date(today);
weekAgo.setDate(weekAgo.getDate() - 7);
const results = await searchByDateRange(
'example',
'INBOX',
weekAgo.toISOString().split('T')[0], // 2025-10-06
today.toISOString().split('T')[0] // 2025-10-13
);
Search Operators
Text Search Operators
subject - Subject contains text
{ search: { subject: 'invoice' } }
body - Message body contains text
{ search: { body: 'payment' } }
from - Sender address contains text
{ search: { from: 'john@example.com' } }
to - Recipient address contains text
{ search: { to: 'jane@company.com' } }
header - Custom header search (object format)
{ search: { header: { 'X-Custom-Header': 'value' } } }
Date Operators
since - Messages on or after date (YYYY-MM-DD)
{ search: { since: '2025-01-01' } }
before - Messages before date (YYYY-MM-DD)
{ search: { before: '2025-12-31' } }
sentSince - Messages sent on or after date (YYYY-MM-DD)
{ search: { sentSince: '2025-01-01' } }
sentBefore - Messages sent before date (YYYY-MM-DD)
{ search: { sentBefore: '2025-12-31' } }
Size Operators
larger - Messages larger than size (bytes)
{ search: { larger: 1000000 } } // > 1MB
smaller - Messages smaller than size (bytes)
{ search: { smaller: 10000 } } // < 10KB
Flag Operators
seen - Messages that are read
{ search: { seen: true } }
unseen - Messages that are unread
{ search: { unseen: true } }
flagged - Messages that are flagged/starred
{ search: { flagged: true } }
answered - Messages that have been replied to
{ search: { answered: true } }
draft - Draft messages
{ search: { draft: true } }
UID and ID Operators
uid - Specific UID or range (IMAP only)
{ search: { uid: '12345' } } // Single UID
{ search: { uid: '12345:12400' } } // UID range
{ search: { uid: '12345:*' } } // From UID to latest
seq - Sequence number range (IMAP only)
{ search: { seq: '1:100' } } // First 100 messages
emailId - Gmail Email ID (Gmail/modern IMAP only)
{ search: { emailId: 'abc123def456' } }
threadId - Thread/conversation ID (Gmail/modern IMAP only)
{ search: { threadId: 'thread_xyz789' } }
emailIds - Multiple specific Email IDs (Gmail/modern IMAP only)
{ search: { emailIds: ['id1', 'id2', 'id3'] } }
Gmail-Specific Operators
gmailRaw - Native Gmail search syntax (Gmail API only)
{ search: { gmailRaw: 'is:unread category:primary' } }
{ search: { gmailRaw: 'from:boss@company.com has:attachment' } }
This allows using Gmail's powerful search operators directly. See Gmail search operators.
Advanced IMAP Operators
modseq - Modification sequence (IMAP with CONDSTORE extension)
{ search: { modseq: 12345 } } // Messages modified after sequence 12345
deleted - Messages marked for deletion (IMAP only, not Gmail)
{ search: { deleted: true } }
gmailRaw: Gmail API onlyemailId,threadId,emailIds: Gmail and modern IMAP servers with RFC 8474 supportseq,modseq,deleted: Traditional IMAP only- Microsoft Graph API limitations:
to,cc,bcc,larger,smallerfields are not supported for search by default- Use
fromandsubjectfor filtering, or searchbodyfor recipient names - Consider using IMAP/SMTP mode for Outlook accounts if these search fields are required
- Alternatively, use the
useOutlookSearchquery parameter to enable MS Graph$searchmode, which supportsto,cc,bcc,larger,smaller,body,before,sentBefore,since, andsentSincefields. Note:$searchreturns up to 1,000 results sorted by relevance (not date) and does not indicate total matches or pages.
Combined Searches
Multiple Criteria (AND logic)
Combine multiple search criteria - all must match:
async function searchUnreadFromSender(accountId, folderPath, sender) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
from: sender,
unseen: true
}
})
}
);
return await response.json();
}
// Find unread messages from john@example.com
const results = await searchUnreadFromSender('example', 'INBOX', 'john@example.com');
Complex Search Example
Find recent large unread invoices:
async function searchRecentLargeInvoices(accountId) {
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=INBOX`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
subject: 'invoice',
unseen: true,
larger: 500000, // > 500KB
since: sevenDaysAgo.toISOString().split('T')[0]
}
})
}
);
return await response.json();
}
const invoices = await searchRecentLargeInvoices('example');
console.log(`Found ${invoices.messages.length} large unread invoices from last 7 days`);
Common Search Patterns
Find Messages with Attachments
async function searchWithAttachments(accountId, folderPath) {
// Note: Not all IMAP servers support attachment search
// Alternative: List messages and filter by hasAttachments field
const params = new URLSearchParams({
path: folderPath,
pageSize: 100
});
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/messages?${params}`,
{
headers: { 'Authorization': 'Bearer YOUR_ACCESS_TOKEN' }
}
);
const data = await response.json();
// Filter messages with attachments
return data.messages.filter(msg => msg.hasAttachments);
}
Search by Message ID
Find a specific message by its Message-ID header:
async function findByMessageId(accountId, messageId) {
// Search across all folders
const folders = ['INBOX', 'Sent', 'Archive'];
for (const folder of folders) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folder}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
header: { 'Message-ID': messageId }
}
})
}
);
const data = await response.json();
if (data.messages && data.messages.length > 0) {
return {
folder: folder,
message: data.messages[0]
};
}
}
return null; // Not found
}
const result = await findByMessageId('example', '<abc123@example.com>');
if (result) {
console.log(`Found in ${result.folder}: ${result.message.subject}`);
}
Search Today's Messages
async function searchTodaysMessages(accountId, folderPath) {
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
const tomorrowStr = tomorrow.toISOString().split('T')[0];
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
since: todayStr,
before: tomorrowStr
}
})
}
);
return await response.json();
}
Search This Month
async function searchThisMonth(accountId, folderPath) {
const now = new Date();
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
since: firstDay.toISOString().split('T')[0],
before: lastDay.toISOString().split('T')[0]
}
})
}
);
return await response.json();
}
Search Unread Important Messages
async function searchUnreadImportant(accountId) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=INBOX`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
unseen: true,
flagged: true
}
})
}
);
return await response.json();
}
Search by Keywords
Search for messages containing specific keywords in the message body:
async function searchByKeywords(accountId, folderPath, keywords) {
// Search in message body for each keyword
const results = [];
for (const keyword of keywords) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: {
body: keyword
}
})
}
);
const data = await response.json();
results.push(...data.messages);
}
// Remove duplicates
const uniqueMessages = Array.from(
new Map(results.map(msg => [msg.id, msg])).values()
);
return uniqueMessages;
}
// Search for messages about invoices or payments
const results = await searchByKeywords('example', 'INBOX', ['invoice', 'payment', 'bill']);
Advanced Search Techniques
Search with Pagination
Handle large search results:
async function searchWithPagination(accountId, folderPath, searchCriteria, pageSize = 20) {
const allResults = [];
let page = 0;
let hasMore = true;
while (hasMore) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}&page=${page}&pageSize=${pageSize}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: searchCriteria
})
}
);
const data = await response.json();
allResults.push(...data.messages);
page++;
hasMore = page < data.pages;
}
return allResults;
}
// Find all unread messages (might be hundreds)
const allUnread = await searchWithPagination('example', 'INBOX', {
unseen: true
}, 100);
Search Multiple Folders
Search across multiple folders:
async function searchMultipleFolders(accountId, folders, searchCriteria) {
const results = await Promise.all(
folders.map(async (folder) => {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folder}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: searchCriteria
})
}
);
const data = await response.json();
return data.messages.map(msg => ({ ...msg, folder }));
})
);
return results.flat();
}
// Search for "invoice" in Inbox and Archive
const results = await searchMultipleFolders('example', ['INBOX', 'Archive'], {
subject: 'invoice'
});
Search and Process
Search and immediately process results:
async function searchAndProcess(accountId, folderPath, searchCriteria, processor) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
search: searchCriteria
})
}
);
const data = await response.json();
for (const message of data.messages) {
try {
await processor(accountId, message);
} catch (err) {
console.error(`Failed to process ${message.id}:`, err);
}
}
return data.messages.length;
}
// Archive all read messages older than 30 days
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const archived = await searchAndProcess(
'example',
'INBOX',
{
seen: true,
before: thirtyDaysAgo.toISOString().split('T')[0]
},
async (accountId, message) => {
await archiveMessage(accountId, message.id);
console.log(`Archived: ${message.subject}`);
}
);
console.log(`Archived ${archived} old messages`);
Search Performance Tips
1. Use Specific Search Criteria
async function search(accountId, folderPath, searchCriteria) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({ search: searchCriteria })
}
);
return await response.json();
}
// Slow - searches entire message body
const results = await search('example', 'INBOX', {
body: 'meeting'
});
// Faster - limits search to subject only
const results = await search('example', 'INBOX', {
subject: 'meeting'
});
// Even faster - adds date constraint
const results = await search('example', 'INBOX', {
subject: 'meeting',
since: '2025-10-01'
});
2. Search Smaller Folders First
async function smartSearch(accountId, searchParams) {
// Try INBOX first (usually smaller than Archive)
let results = await search(accountId, 'INBOX', searchParams);
if (results.messages.length === 0) {
// Only search archive if nothing found
results = await search(accountId, 'Archive', searchParams);
}
return results;
}
3. Use Date Ranges
Always limit searches with date ranges when possible:
// Bad - searches all messages (could be years worth)
async function search(accountId, folderPath, searchCriteria) {
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({ search: searchCriteria })
}
);
return await response.json();
}
const results = await search('example', 'INBOX', {
from: 'john@example.com'
});
// Good - limits to last 90 days
const ninetyDaysAgo = new Date();
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
const results = await search('example', 'INBOX', {
from: 'john@example.com',
since: ninetyDaysAgo.toISOString().split('T')[0]
});
4. Cache Search Results
class SearchCache {
constructor(ttl = 60000) {
this.cache = new Map();
this.ttl = ttl;
}
key(accountId, folderPath, searchCriteria) {
return JSON.stringify({ accountId, folderPath, searchCriteria });
}
get(accountId, folderPath, searchCriteria) {
const k = this.key(accountId, folderPath, searchCriteria);
const cached = this.cache.get(k);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.results;
}
return null;
}
set(accountId, folderPath, searchCriteria, results) {
const k = this.key(accountId, folderPath, searchCriteria);
this.cache.set(k, {
results,
timestamp: Date.now()
});
// Clean up old entries
if (this.cache.size > 100) {
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
}
}
const searchCache = new SearchCache(60000); // 1 min TTL
async function cachedSearch(accountId, folderPath, searchCriteria) {
const cached = searchCache.get(accountId, folderPath, searchCriteria);
if (cached) return cached;
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/search?path=${folderPath}`,
{
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({ search: searchCriteria })
}
);
const results = await response.json();
searchCache.set(accountId, folderPath, searchCriteria, results);
return results;
}
Provider-Specific Considerations
Gmail API
Gmail API search has some differences:
- Labels are used instead of folders
- All messages are in
[Gmail]/All Mail - Use
labelsfield to filter by label
async function searchGmailByLabel(accountId, label) {
const params = new URLSearchParams({
path: '[Gmail]/All Mail'
});
const response = await fetch(
`https://your-emailengine.com/v1/account/${accountId}/messages?${params}`,
{
headers: { 'Authorization': 'Bearer YOUR_ACCESS_TOKEN' }
}
);
const data = await response.json();
// Filter by label
return data.messages.filter(msg =>
msg.labels && msg.labels.includes(label)
);
}
IMAP Limitations
Some IMAP servers have limitations:
- Body search might not be supported
- Date search formats may vary
- Some servers don't support all flags
Always test searches with your specific provider.