infiniflow/ragflow · error · RuntimeError

Failed to fetch email ids; {status=}

Error message

Failed to fetch email ids; {status=}

What it means

Raised by _fetch_email_ids_in_mailbox when mail_client.search returns a non-OK status or an empty response list. The date-window search (SINCE/BEFORE built from the start/end epoch args) failed at the protocol level. Note that an empty-but-OK search (no mail in range) returns normally - this error means the command itself failed.

Source

Thrown at common/data_source/imap_connector.py:450

    mailbox: str,
    start: SecondsSinceUnixEpoch,
    end: SecondsSinceUnixEpoch,
) -> list[str]:
    if not _select_mailbox(mail_client, mailbox):
        logging.warning(f"Skip mailbox: {mailbox}")
        return []

    start_dt = datetime.fromtimestamp(start, tz=timezone.utc)
    end_dt = datetime.fromtimestamp(end, tz=timezone.utc) + timedelta(days=1)

    start_str = start_dt.strftime("%d-%b-%Y")
    end_str = end_dt.strftime("%d-%b-%Y")
    search_criteria = f'(SINCE "{start_str}" BEFORE "{end_str}")'

    status, email_ids_byte_array = mail_client.search(None, search_criteria)

    if status != _IMAP_OKAY_STATUS or not email_ids_byte_array:
        raise RuntimeError(f"Failed to fetch email ids; {status=}")

    email_ids: bytes = email_ids_byte_array[0]

    return [email_id.decode() for email_id in email_ids.split()]


def _fetch_email(mail_client: imaplib.IMAP4_SSL, email_id: str) -> Message | None:
    status, msg_data = mail_client.fetch(message_set=email_id, message_parts="(RFC822)")
    if status != _IMAP_OKAY_STATUS or not msg_data:
        return None

    data = msg_data[0]
    if not isinstance(data, tuple):
        raise RuntimeError(f"Message data should be a tuple; instead got a {type(data)=} {data=}")

    _, raw_email = data
    return email.message_from_bytes(raw_email)

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Retry with a freshly created mail client (re-login) and re-select the mailbox before searching.
  2. Verify the mailbox still exists immediately before searching if folders change frequently.
  3. Serialize access so only one operation uses an IMAP connection at a time.
  4. Reproduce the SEARCH manually to see the server's response text if failures are persistent.

Example fix

# before
ids = _fetch_email_ids_in_mailbox(client, mailbox, start, end)  # RuntimeError mid-sync

# after
try:
    ids = _fetch_email_ids_in_mailbox(client, mailbox, start, end)
except RuntimeError:
    client = connector._get_mail_client()
    client.select(mailbox, readonly=True)
    ids = _fetch_email_ids_in_mailbox(client, mailbox, start, end)
Defensive patterns

Strategy: retry

Try / catch

try:
    ids = _fetch_email_ids_in_mailbox(client, mailbox, start, end)
except RuntimeError as e:
    if 'Failed to fetch email ids' not in str(e):
        raise
    client = connector._get_mail_client()
    client.select(mailbox, readonly=True)
    ids = _fetch_email_ids_in_mailbox(client, mailbox, start, end)

Prevention

When it happens

Trigger: Session dropped or mailbox deselected before SEARCH; the server rejects the search criteria format; server under load returning 'NO'. Triggered per-mailbox during checkpoint processing when message ids need to be enumerated.

Common situations: Intermittent disconnects on long syncs; servers rejecting the quoted date format due to locale differences; two jobs sharing one IMAP connection concurrently; the mailbox being deleted or renamed between LIST and SEARCH.

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/9e63bf7533844b35. Report an issue: GitHub.