Skip to main content
Glama
ai-zerolab

MCP Email Server

by ai-zerolab

list_emails_metadata

Retrieve email metadata including subject, sender, recipients, and date for filtering and pagination, returning email IDs to access full content.

Instructions

List email metadata (email_id, subject, sender, recipients, date) without body content. Returns email_id for use with get_emails_content.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
account_nameYesThe name of the email account.
pageNoThe page number to retrieve (starting from 1).
page_sizeNoThe number of emails to retrieve per page.
beforeNoRetrieve emails before this datetime (UTC).
sinceNoRetrieve emails since this datetime (UTC).
subjectNoFilter emails by subject.
from_addressNoFilter emails by sender address.
to_addressNoFilter emails by recipient address.
orderNoOrder emails by field. `asc` or `desc`.desc
mailboxNoThe mailbox to retrieve emails from.INBOX

Implementation Reference

  • The main MCP tool handler and registration for 'list_emails_metadata'. Dispatches to account handler's get_emails_metadata method.
    @mcp.tool(
        description="List email metadata (email_id, subject, sender, recipients, date) without body content. Returns email_id for use with get_emails_content."
    )
    async def list_emails_metadata(
        account_name: Annotated[str, Field(description="The name of the email account.")],
        page: Annotated[
            int,
            Field(default=1, description="The page number to retrieve (starting from 1)."),
        ] = 1,
        page_size: Annotated[int, Field(default=10, description="The number of emails to retrieve per page.")] = 10,
        before: Annotated[
            datetime | None,
            Field(default=None, description="Retrieve emails before this datetime (UTC)."),
        ] = None,
        since: Annotated[
            datetime | None,
            Field(default=None, description="Retrieve emails since this datetime (UTC)."),
        ] = None,
        subject: Annotated[str | None, Field(default=None, description="Filter emails by subject.")] = None,
        from_address: Annotated[str | None, Field(default=None, description="Filter emails by sender address.")] = None,
        to_address: Annotated[
            str | None,
            Field(default=None, description="Filter emails by recipient address."),
        ] = None,
        order: Annotated[
            Literal["asc", "desc"],
            Field(default=None, description="Order emails by field. `asc` or `desc`."),
        ] = "desc",
        mailbox: Annotated[str, Field(default="INBOX", description="The mailbox to retrieve emails from.")] = "INBOX",
    ) -> EmailMetadataPageResponse:
        handler = dispatch_handler(account_name)
    
        return await handler.get_emails_metadata(
            page=page,
            page_size=page_size,
            before=before,
            since=since,
            subject=subject,
            from_address=from_address,
            to_address=to_address,
            order=order,
            mailbox=mailbox,
        )
  • Pydantic models EmailMetadata and EmailMetadataPageResponse defining the input parameters and output structure for list_emails_metadata.
    class EmailMetadata(BaseModel):
        """Email metadata"""
    
        email_id: str
        message_id: str | None = None  # RFC 5322 Message-ID header for reply threading
        subject: str
        sender: str
        recipients: list[str]  # Recipient list
        date: datetime
        attachments: list[str]
    
        @classmethod
        def from_email(cls, email: dict[str, Any]):
            return cls(
                email_id=email["email_id"],
                message_id=email.get("message_id"),
                subject=email["subject"],
                sender=email["from"],
                recipients=email.get("to", []),
                date=email["date"],
                attachments=email["attachments"],
            )
    
    
    class EmailMetadataPageResponse(BaseModel):
        """Paged email metadata response"""
    
        page: int
        page_size: int
        before: datetime | None
        since: datetime | None
        subject: str | None
        emails: list[EmailMetadata]
        total: int
  • dispatch_handler selects and returns the appropriate EmailHandler (ClassicEmailHandler) for the given account_name.
    def dispatch_handler(account_name: str) -> EmailHandler:
        settings = get_settings()
        account = settings.get_account(account_name)
        if isinstance(account, ProviderSettings):
            raise NotImplementedError
        if isinstance(account, EmailSettings):
            return ClassicEmailHandler(account)
    
        raise ValueError(f"Account {account_name} not found, available accounts: {settings.get_accounts()}")
  • ClassicEmailHandler.get_emails_metadata collects metadata from the stream, computes total count, and constructs the paged response.
    async def get_emails_metadata(
        self,
        page: int = 1,
        page_size: int = 10,
        before: datetime | None = None,
        since: datetime | None = None,
        subject: str | None = None,
        from_address: str | None = None,
        to_address: str | None = None,
        order: str = "desc",
        mailbox: str = "INBOX",
    ) -> EmailMetadataPageResponse:
        emails = []
        async for email_data in self.incoming_client.get_emails_metadata_stream(
            page, page_size, before, since, subject, from_address, to_address, order, mailbox
        ):
            emails.append(EmailMetadata.from_email(email_data))
        total = await self.incoming_client.get_email_count(
            before, since, subject, from_address=from_address, to_address=to_address, mailbox=mailbox
        )
        return EmailMetadataPageResponse(
            page=page,
            page_size=page_size,
            before=before,
            since=since,
            subject=subject,
            emails=emails,
            total=total,
        )
  • EmailClient.get_emails_metadata_stream performs the actual IMAP operations: search, paginate, fetch headers, parse and yield individual email metadata.
    async def get_emails_metadata_stream(  # noqa: C901
        self,
        page: int = 1,
        page_size: int = 10,
        before: datetime | None = None,
        since: datetime | None = None,
        subject: str | None = None,
        from_address: str | None = None,
        to_address: str | None = None,
        order: str = "desc",
        mailbox: str = "INBOX",
    ) -> AsyncGenerator[dict[str, Any], None]:
        imap = self.imap_class(self.email_server.host, self.email_server.port)
        try:
            # Wait for the connection to be established
            await imap._client_task
            await imap.wait_hello_from_server()
    
            # Login and select inbox
            await imap.login(self.email_server.user_name, self.email_server.password)
            try:
                await imap.id(name="mcp-email-server", version="1.0.0")
            except Exception as e:
                logger.warning(f"IMAP ID command failed: {e!s}")
            await imap.select(mailbox)
    
            search_criteria = self._build_search_criteria(
                before, since, subject, from_address=from_address, to_address=to_address
            )
            logger.info(f"Get metadata: Search criteria: {search_criteria}")
    
            # Search for messages - use UID SEARCH for better compatibility
            _, messages = await imap.uid_search(*search_criteria)
    
            # Handle empty or None responses
            if not messages or not messages[0]:
                logger.warning("No messages returned from search")
                email_ids = []
            else:
                email_ids = messages[0].split()
                logger.info(f"Found {len(email_ids)} email IDs")
            start = (page - 1) * page_size
            end = start + page_size
    
            if order == "desc":
                email_ids.reverse()
    
            # Fetch each message's metadata only
            for _, email_id in enumerate(email_ids[start:end]):
                try:
                    # Convert email_id from bytes to string
                    email_id_str = email_id.decode("utf-8")
    
                    # Fetch only headers to get metadata without body
                    _, data = await imap.uid("fetch", email_id_str, "BODY.PEEK[HEADER]")
    
                    if not data:
                        logger.error(f"Failed to fetch headers for UID {email_id_str}")
                        continue
    
                    # Find the email headers in the response
                    raw_headers = None
                    if len(data) > 1 and isinstance(data[1], bytearray):
                        raw_headers = bytes(data[1])
                    else:
                        # Search through all items for header content
                        for item in data:
                            if isinstance(item, bytes | bytearray) and len(item) > 10:
                                # Skip IMAP protocol responses
                                if isinstance(item, bytes) and b"FETCH" in item:
                                    continue
                                # This is likely the header content
                                raw_headers = bytes(item) if isinstance(item, bytearray) else item
                                break
    
                    if raw_headers:
                        try:
                            # Parse headers only
                            parser = BytesParser(policy=default)
                            email_message = parser.parsebytes(raw_headers)
    
                            # Extract metadata
                            subject = email_message.get("Subject", "")
                            sender = email_message.get("From", "")
                            date_str = email_message.get("Date", "")
    
                            # Extract recipients
                            to_addresses = []
                            to_header = email_message.get("To", "")
                            if to_header:
                                to_addresses = [addr.strip() for addr in to_header.split(",")]
    
                            cc_header = email_message.get("Cc", "")
                            if cc_header:
                                to_addresses.extend([addr.strip() for addr in cc_header.split(",")])
    
                            # Parse date
                            try:
                                date_tuple = email.utils.parsedate_tz(date_str)
                                date = (
                                    datetime.fromtimestamp(email.utils.mktime_tz(date_tuple), tz=timezone.utc)
                                    if date_tuple
                                    else datetime.now(timezone.utc)
                                )
                            except Exception:
                                date = datetime.now(timezone.utc)
    
                            # For metadata, we don't fetch attachments to save bandwidth
                            # We'll mark it as unknown for now
                            metadata = {
                                "email_id": email_id_str,
                                "subject": subject,
                                "from": sender,
                                "to": to_addresses,
                                "date": date,
                                "attachments": [],  # We don't fetch attachment info for metadata
                            }
                            yield metadata
                        except Exception as e:
                            # Log error but continue with other emails
                            logger.error(f"Error parsing email metadata: {e!s}")
                    else:
                        logger.error(f"Could not find header data in response for email ID: {email_id_str}")
                except Exception as e:
                    logger.error(f"Error fetching email metadata {email_id}: {e!s}")
        finally:
            # Ensure we logout properly
            try:
                await imap.logout()
            except Exception as e:
                logger.info(f"Error during logout: {e}")

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ai-zerolab/mcp-email-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server