You’re using Python’s imaplib to search emails, but mail.search() keeps returning empty results even though you know matching emails exist. Here’s why it happens and how to fix it.

The Problem

1
2
3
4
5
6
7
8
9
import imaplib

mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('user@gmail.com', 'app-password')
mail.select('INBOX')

# This returns nothing!
status, messages = mail.search(None, 'FROM "john@example.com" SUBJECT "invoice"')
print(messages)  # [b'']

You’ve verified the emails exist. You can see them in Gmail. But search() returns an empty byte string.

Common Causes and Fixes

1. Wrong Search Syntax (Most Common)

IMAP search syntax is specific and unforgiving. The search string you pass to mail.search() is sent directly to the IMAP server — it’s not Python string matching.

Wrong:

1
2
# Combining criteria incorrectly
mail.search(None, 'FROM "john" SUBJECT "invoice"')

Right:

1
2
# Each criterion is a separate argument, or use parentheses
mail.search(None, '(FROM "john" SUBJECT "invoice")')

For OR conditions, use the OR keyword before the criteria:

1
2
3
4
5
# Wrong - this won't work
mail.search(None, 'FROM "john" OR FROM "jane"')

# Right - OR precedes the two criteria it joins
mail.search(None, 'OR FROM "john" FROM "jane"')

2. Searching the Wrong Folder

Gmail uses [Gmail]/All Mail for everything, but INBOX only contains emails actually in your inbox (not archived).

1
2
3
4
5
6
7
# Only searches inbox
mail.select('INBOX')

# Searches all mail including archived
mail.select('"[Gmail]/All Mail"')

# Note the quotes inside quotes - required for folder names with spaces/special chars

List available folders to see what’s there:

1
2
3
status, folders = mail.list()
for folder in folders:
    print(folder.decode())

3. Case Sensitivity Issues

IMAP servers vary in how they handle case sensitivity. Gmail is case-insensitive for most searches, but other servers may not be.

1
2
3
4
5
# May not match "John@Example.com"
mail.search(None, 'FROM "john@example.com"')

# Search the body instead for more flexibility
mail.search(None, 'BODY "john"')

4. Date Format Problems

IMAP dates must be in a specific format: DD-Mon-YYYY

1
2
3
4
5
6
# Wrong
mail.search(None, 'SINCE "2026-03-01"')
mail.search(None, 'SINCE "03/01/2026"')

# Right
mail.search(None, 'SINCE "01-Mar-2026"')

5. TEXT vs BODY vs Subject/From

  • TEXT searches everything (headers + body)
  • BODY searches only the message body
  • FROM, SUBJECT, etc. search specific headers
1
2
3
4
5
6
7
8
# Search everywhere for a string
mail.search(None, 'TEXT "project update"')

# Search only the body
mail.search(None, 'BODY "project update"')

# Search only subject line
mail.search(None, 'SUBJECT "project update"')

Complete Working Example

Here’s a robust search function that handles common edge cases:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import imaplib
import email
from email.header import decode_header

def search_emails(credentials, search_criteria, folder='"[Gmail]/All Mail"'):
    """
    Search emails with proper error handling.
    
    Args:
        credentials: dict with 'email', 'password', 'server'
        search_criteria: IMAP search string, e.g., '(FROM "john" SUBJECT "invoice")'
        folder: IMAP folder to search
    
    Returns:
        List of message IDs
    """
    mail = imaplib.IMAP4_SSL(credentials.get('server', 'imap.gmail.com'))
    
    try:
        mail.login(credentials['email'], credentials['password'])
        
        # Select folder - returns count of messages
        status, data = mail.select(folder, readonly=True)
        if status != 'OK':
            print(f"Failed to select folder: {folder}")
            return []
        
        # Perform search
        status, messages = mail.search(None, search_criteria)
        if status != 'OK':
            print(f"Search failed: {status}")
            return []
        
        # messages[0] is a space-separated byte string of IDs
        msg_ids = messages[0].split()
        return msg_ids
        
    except imaplib.IMAP4.error as e:
        print(f"IMAP error: {e}")
        return []
    finally:
        try:
            mail.logout()
        except:
            pass

# Usage
creds = {
    'email': 'you@gmail.com',
    'password': 'your-app-password',
    'server': 'imap.gmail.com'
}

# Search with proper syntax
results = search_emails(creds, '(FROM "client@example.com" SINCE "01-Mar-2026")')
print(f"Found {len(results)} emails")

IMAP Search Criteria Quick Reference

CriteriaExampleMeaning
ALLALLAll messages
UNSEENUNSEENUnread messages
FROMFROM "john"From contains “john”
TOTO "me@example.com"To contains address
SUBJECTSUBJECT "meeting"Subject contains “meeting”
BODYBODY "keyword"Body contains “keyword”
TEXTTEXT "keyword"Anywhere in message
SINCESINCE "01-Mar-2026"After date
BEFOREBEFORE "31-Mar-2026"Before date
FLAGGEDFLAGGEDStarred/flagged
OROR FROM "a" FROM "b"Either condition
NOTNOT SEENNegation

Still Getting Empty Results?

  1. Test with a broader search first: mail.search(None, 'ALL') — if this returns nothing, you’re in the wrong folder
  2. Check folder names: Use mail.list() to see exact folder names
  3. Verify credentials: Wrong app password = silent failures on some servers
  4. Try the Gmail API instead: For complex searches, Gmail’s API is more reliable than IMAP

The IMAP protocol is old and quirky, but once you understand the syntax rules, it works reliably. The key is remembering that the search string is passed directly to the server — Python doesn’t interpret it.