Skip to content

Instantly share code, notes, and snippets.

@thomaswieland
Created February 17, 2018 13:56
Show Gist options
  • Save thomaswieland/3cac92843896040b11c4635f7bf61cfb to your computer and use it in GitHub Desktop.
Save thomaswieland/3cac92843896040b11c4635f7bf61cfb to your computer and use it in GitHub Desktop.
Python: IMAP IDLE with imaplib2
import imaplib2, time
from threading import *
# This is the threading object that does all the waiting on
# the event
class Idler(object):
def __init__(self, conn):
self.thread = Thread(target=self.idle)
self.M = conn
self.event = Event()
def start(self):
self.thread.start()
def stop(self):
# This is a neat trick to make thread end. Took me a
# while to figure that one out!
self.event.set()
def join(self):
self.thread.join()
def idle(self):
# Starting an unending loop here
while True:
# This is part of the trick to make the loop stop
# when the stop() command is given
if self.event.isSet():
return
self.needsync = False
# A callback method that gets called when a new
# email arrives. Very basic, but that's good.
def callback(args):
if not self.event.isSet():
self.needsync = True
self.event.set()
# Do the actual idle call. This returns immediately,
# since it's asynchronous.
self.M.idle(callback=callback)
# This waits until the event is set. The event is
# set by the callback, when the server 'answers'
# the idle call and the callback function gets
# called.
self.event.wait()
# Because the function sets the needsync variable,
# this helps escape the loop without doing
# anything if the stop() is called. Kinda neat
# solution.
if self.needsync:
self.event.clear()
self.dosync()
# The method that gets called when a new email arrives.
# Replace it with something better.
def dosync(self):
print "Got an event!"
# Had to do this stuff in a try-finally, since some testing
# went a little wrong.....
try:
# Set the following two lines to your creds and server
M = imaplib2.IMAP4_SSL("mail.example.com")
M.login("mylogin","mypassword")
# We need to get out of the AUTH state, so we just select
# the INBOX.
M.select("INBOX")
# Start the Idler thread
idler = Idler(M)
idler.start()
# Because this is just an example, exit after 1 minute.
time.sleep(1*60)
finally:
# Clean up.
idler.stop()
idler.join()
M.close()
# This is important!
M.logout()
@blessed-tawanda
Copy link

Thank You i have been looking for this code please keep posting stuff like this

@clockzhong
Copy link

How to install imaplib2 in python3 env? I've installed it as:

pip3 install imaplib2

but when executing:
M = imaplib2.IMAP4_SSL("mail.example.com")
It report error as:

Traceback (most recent call last):
File "", line 1, in
AttributeError: module 'imaplib2' has no attribute 'IMAP4_SSL'

@clockzhong
Copy link

Ok, I've found the bug's root cause. Ubuntu18.*'s pip3 repos don't have the correct pakages for imaplib2, so the "pip3 install imaplib2" couldn't install the correct package. I've changed to the following command to install it:

sudo apt install python3-imaplib2
Then it works fine!!!

@clockzhong
Copy link

Still couldn't pass the first line code, it's still in disaster:

>>> M = imaplib2.IMAP4_SSL("imap-mail.outlook.com")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/imaplib2.py", line 2192, in __init__
    IMAP4.__init__(self, host, port, debug, debug_file, identifier, timeout, debug_buf_lvl)
  File "/usr/lib/python3/dist-packages/imaplib2.py", line 400, in __init__
    self.welcome = self._request_push(name='welcome', tag='continuation').get_response('IMAP4 protocol error: %s')[1]
  File "/usr/lib/python3/dist-packages/imaplib2.py", line 201, in get_response
    raise typ(exc_fmt % str(val))
imaplib2.error: IMAP4 protocol error: program error: <class 'TypeError'> - cannot use a bytes pattern on a string-like object

@clockzhong
Copy link

Ok, got the root cause of the new error. The pip3 repos also makes serious mistakes on the installing package. It's not for python3, it uses the python2's source codes as the python3's platform.
I've used the source codes provided here:
https://github.com/imaplib2/imaplib2/tree/master/imaplib2
and copied the imaplib2.py3 as imaplib2.py into my PYTHONPATH, then everything works fine.

@jaynery
Copy link

jaynery commented Jul 16, 2020

@clockzhong

Could you explain in a little more detail what steps you've followed? I'm trying to get imaplib2 working as well and am coming across the same problem. I've followed the steps you've mentioned and am seeing the same error.

Traceback (most recent call last):
  File "./test_imap.py", line 48, in <module>
    M = imaplib2.IMAP4_SSL(address)
  File "/usr/lib/python3/dist-packages/imaplib2.py", line 2192, in __init__
    IMAP4.__init__(self, host, port, debug, debug_file, identifier, timeout, debug_buf_lvl)
  File "/usr/lib/python3/dist-packages/imaplib2.py", line 400, in __init__
    self.welcome = self._request_push(name='welcome', tag='continuation').get_response('IMAP4 protocol error: %s')[1]
  File "/usr/lib/python3/dist-packages/imaplib2.py", line 201, in get_response
    raise typ(exc_fmt % str(val))
imaplib2.error: IMAP4 protocol error: program error: <class 'TypeError'> - cannot use a bytes pattern on a string-like object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./test_imap.py", line 57, in <module>
    idler.stop()
NameError: name 'idler' is not defined

I went to the link and copied imaplib2.py3 as imaplib2.py to the library path. Is that the same path your're referring to by PYTHONPATH?

@clockzhong
Copy link

@jaynery , No, I didn't use pip to install the imaplib2, I just use its project source tree locally, and set the PYTHONPATH to point to its path. I don't rely on the pip installation, because sometimes the pip pakcages' authors couldn't handle the environment dependent factors correctly. Especially these days, the python2 will disappear, and we must switch all python packages from python2 to python3, some authors already don't want to touch their python project which still only support python2. This package, imaplib2 is a good example for this situation.

@jaynery
Copy link

jaynery commented Jul 21, 2020

@clockzhong Thanks so much! Was able to get the library working.
@thomaswieland After an event is received, how would you go about starting the IDLE over again? I have the script set up so that it executes a function from another script. After dosync() is executed the script goes into sleep. Any further events won't trigger the dosync(). Do you have any ideas for a work around?

@andydoc
Copy link

andydoc commented Dec 7, 2020

@jaynery I have written my own, less polished code for my own use case and at the beginning of the equivalent of dosync() I do idle_done(), at the end I do idle()

@ydf
Copy link

ydf commented Aug 17, 2021


    def get_unseen_messages(self) -> list:
        typ, data = self.M.search(None, 'UNSEEN')
        if typ == u'OK':
            return data[0].split()
        raise ValueError("Get unseen messages Error")

    def get_message(self, message_number):
        typ, data = self.M.fetch(message_number, 'RFC822')
        if typ == 'OK':
            return email.message_from_bytes(data[0][1])
        raise Exception

    def dosync(self):
        message_numbers = self.get_unseen_messages()
        message_numbers.reverse()
        for message_number in message_numbers:
            mail_obj = self.get_message(message_number)
            print(mail_obj)

@bfontaine
Copy link

bfontaine commented Jan 5, 2023

If you can use a library, this one has built-in IDLE support:
https://imapclient.readthedocs.io/en/2.3.1/advanced.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment