0

I am using the following code to send and search emails in Gmail using Python,

import smtplib
import imaplib
import email
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import pandas as pd


class EmailClient:
    def __init__(self, email, password, smtp_server='smtp.gmail.com', smtp_port=587, imap_server="imap.gmail.com"):
    self.email = email
    self.password = password
    self.smtp_server = smtplib.SMTP(smtp_server, smtp_port)
    self.imap_server = imaplib.IMAP4_SSL(imap_server)

def send_email(self, to_addr, subject, body):
    msg = MIMEMultipart()
    msg['From'] = self.email
    msg['To'] = to_addr
    msg['Subject'] = subject
    msg.attach(MIMEText(body, 'plain'))

    self.smtp_server.starttls()
    self.smtp_server.login(self.email, self.password)
    self.smtp_server.send_message(msg)
    self.smtp_server.quit()

def search_email(self, mailbox="INBOX", subject=None, to=None, from_=None, since_date=None, until_date=None,
                 since_emailid=None):

    self.imap_server.login(self.email, self.password)

    self.imap_server.select(mailbox)

    query_parts = []
    if subject is not None:
        query_parts.append(f'(SUBJECT "{subject}")')
    if to is not None:
        query_parts.append(f'(TO "{to}")')
    if from_ is not None:
        query_parts.append(f'(FROM "{from_}")')
    if since_date is not None:
        since_date_str = since_date.strftime("%d-%b-%Y")
        query_parts.append(f'(SINCE "{since_date_str}")')
    if until_date is not None:
        until_date_str = until_date.strftime("%d-%b-%Y")
        query_parts.append(f'(BEFORE "{until_date_str}")')
    if since_emailid is not None:
        query_parts.append(f'(UID {since_emailid}:*)')

    query = ' '.join(query_parts)

    ret = []

    resp, items = self.imap_server.uid('search', None, query)
    items = items[0].split()
    for emailid in items[::-1]:
        resp, data = self.imap_server.uid('fetch', emailid, "(BODY[HEADER.FIELDS (SUBJECT TO FROM DATE)])")
        try:
            raw_email = data[0][1].decode("utf-8")
        except UnicodeDecodeError:
            ValueError(f"Could not decode email with id {emailid}")
        email_message = email.message_from_string(raw_email)

        email_line = {}
        email_line['id'] = emailid
        email_line["to"] = email_message['To']
        email_line["from"] = email_message['From']
        email_line["subject"] = str(email_message['Subject'])
        email_line["created_at"] = email_message['Date']
        resp, email_data = self.imap_server.uid('fetch', emailid, '(BODY[TEXT])')
        email_line["body"] = email_data[0][1].decode('utf-8')

        ret.append(email_line)

    self.imap_server.logout()

    return pd.DataFrame(ret)

When I execute,

email_client = EmailClient('<email>',  '<app_password>')
email_client.search_email()

I expected all of the emails in my INBOX to be returned, instead I get the following error,

imaplib.IMAP4.error: UID command error: BAD [b'Could not parse command']

What is the problem here?

I know that the same error has popped up here,
Unable to retrieve gmail messages from any folder other than inbox (Python3 issue)

But, my issue seems to be a little different.

Another question along the same lines, is there a way for me to limit the number of results that are returned when running this function? Something similar to a LIMIT clause? How do I incorporate that to the query?

Minura Punchihewa
  • 1,498
  • 1
  • 12
  • 35
  • What is the actual command that causes the parse error? – arnt Aug 20 '23 at 10:17
  • The [ESEARCH](https://www.rfc-editor.org/rfc/rfc4731)+[PARTIAL](https://datatracker.ietf.org/doc/rfc9394/) extensions let you instruct the server to return fewer results. I haven't even tried to find out how many servers support PARTIAL. – arnt Aug 20 '23 at 10:21

0 Answers0