How to move email using imap-tools

Viewed 2168

I have been trying to write a quick script to move emails from one folder to another in my email. However, I continue getting the error message:

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

from imap_tools import MailBox, Q
from dotenv import load_dotenv
import os
load_dotenv('.env')


def get_emails(folder, move_to, search):
    # get list of email msg_uid from INBOX folder
    with MailBox(imap).login(email, password, initial_folder=folder) as mailbox:
        print('Login Success')

        if mailbox.folder.exists(folder) and mailbox.folder.exists(move_to):

            print('Both folders exist')
            msg_uid = [msg.uid for msg in mailbox.fetch(Q(text=search))]
            print(f'Message count: {len(msg_uid)}')

            for msg in mailbox.fetch():
                print(f'UID: {msg.uid}', f'Subject: {msg.subject}')
                res = mailbox.move(msg.uid, move_to)
        else:
            if not mailbox.folder.exists(folder):
                print('From folder missing')
            if not mailbox.folder.exists(move_to):
                print('To folder missing')


if __name__ == '__main__':
    email = os.getenv('my_gmail_email')
    password = os.getenv('my_gmail_password')
    imap = os.getenv('my_gmail_imap_server')
    folder = 'INBOX/Aqua Living/QuickForm (NOW)'
    move_to = 'INBOX/Aqua Living'
    search_text = 'NOW'

    get_emails(folder, move_to, search_text)
2 Answers

I was finally able to get this to work! Turns out the copy function in imaplib doesn't like folder names with spaces. I simply added formating to fix that.

# formate move_to variable before using it in mailbox.move()
move_to = f'\"{move_to}\"'

for msg in mailbox.fetch():
   print(f'UID: {msg.uid}', f'Subject: {msg.subject}')
   res = mailbox.move(msg.uid, move_to)

1: Actions with emails in folder:

You can use 2 approaches to perform these operations:

  • "in bulk" - Perform IMAP operation for message set per 1 command
  • "by one" - Perform IMAP operation for each message separately per N commands

You are using "by one". Actually this is bad idea - it will make imap command on each msg.

Result of MailBox.fetch generator in actions will be implicitly converted to uid list. There is no need to build uid set manually (in your case)

You can do it like so:

mailbox.move(mailbox.fetch(Q(text=search)), move_to)

It will make 1 imap command.

Note: For actions with a large number of messages imap command may be too large and will throw an exception, use 'limit' argument for fetch in this case.

2: As said Chad Heathcote, use quotes:

move_to = f'\"{move_to}\"'

3: Docs at main page of github and pypi.

--

Best regards, imap_tools author.

Related