31

This is a smaller portion of a bigger project. I need to only get unread emails and a parse their headers. How can I modify the following script to only get unread emails?

conn = imaplib.IMAP4_SSL(imap_server)
conn.login(imap_user, imap_password)

status, messages = conn.select('INBOX')    

if status != "OK":
    print "Incorrect mail box"
    exit()

print messages
David Vasandani
  • 1,840
  • 8
  • 30
  • 53

6 Answers6

47

Something like this will do the trick.

conn = imaplib.IMAP4_SSL(imap_server)

try:
    (retcode, capabilities) = conn.login(imap_user, imap_password)
except:
    print sys.exc_info()[1]
    sys.exit(1)

conn.select(readonly=1) # Select inbox or default namespace
(retcode, messages) = conn.search(None, '(UNSEEN)')
if retcode == 'OK':
    for num in messages[0].split(' '):
        print 'Processing :', message
        typ, data = conn.fetch(num,'(RFC822)')
        msg = email.message_from_string(data[0][1])
        typ, data = conn.store(num,'-FLAGS','\\Seen')
        if ret == 'OK':
            print data,'\n',30*'-'
            print msg

conn.close()

There's also a duplicate question here - Find new messages added to an imap mailbox since I last checked with python imaplib2?

Two useful functions for you to retrieve the body and attachments of the new message you detected (reference: How to fetch an email body using imaplib in python?) -

def getMsgs(servername="myimapserverfqdn"):
  usernm = getpass.getuser()
  passwd = getpass.getpass()
  subject = 'Your SSL Certificate'
  conn = imaplib.IMAP4_SSL(servername)
  conn.login(usernm,passwd)
  conn.select('Inbox')
  typ, data = conn.search(None,'(UNSEEN SUBJECT "%s")' % subject)
  for num in data[0].split():
    typ, data = conn.fetch(num,'(RFC822)')
    msg = email.message_from_string(data[0][1])
    typ, data = conn.store(num,'-FLAGS','\\Seen')
    yield msg

def getAttachment(msg,check):
  for part in msg.walk():
    if part.get_content_type() == 'application/octet-stream':
      if check(part.get_filename()):
        return part.get_payload(decode=1)

PS: If you pass by in 2020 after python 2.7 death: replace email.message_from_string(data[0][1]) with email.message_from_bytes(data[0][1])

Espoir Murhabazi
  • 5,973
  • 5
  • 42
  • 73
Calvin Cheng
  • 35,640
  • 39
  • 116
  • 167
  • Thanks @Calvin! How would I also get the body of the email though? – David Vasandani Nov 03 '12 at 16:07
  • 1
    Updated my answer with two new functions `getMsgs` and `getAttachment` which you can then use in the `for message in messages[0].split(' '):` for-loop. – Calvin Cheng Nov 03 '12 at 16:27
  • I tried `print data` & `print msg` at the bottom to see the contents of the email but it said `is not defined ` What would I time to see the message? – David Vasandani Nov 03 '12 at 18:01
  • updated function to synchronize the variable names. `getMsgs` and `getAttachment` is just for reference. Pls use the original code snippet. – Calvin Cheng Nov 03 '12 at 18:06
  • 2
    A note on marking messages as seen. Changing the line typ, data = conn.store(num,'-FLAGS','\\Seen') to typ, data = conn.store(num,'+FLAGS','\\Seen') fixed it for me. – concentricpuddle Jun 10 '14 at 08:32
  • 9
    where is ret defined? – user1767754 Sep 29 '14 at 09:47
  • 1
    your answer does not work in every case. i try to check for unseen messages and the first message in the list of unseen messages has ``['flags', ('RFC822', 'message')`` while the others have ``[('RFC822', 'message')]``. Either this is some IMAP strangeness, or imaplib2 has a weird output format. – allo Mar 08 '15 at 14:22
  • 1
    What is `check()` ? Python does not find it –  Apr 26 '16 at 08:01
  • Passing by in 2020 +1 – AmourK Jun 14 '20 at 23:48
  • Hello, I used your code to find the email with a specific subject text and it worked. Thanks. But now I am having another issue where the text I am using is in the Spanish language with Unicode or special character ex. "Favor de verificar su correo electrónico". "ó" is the unicode and the search method is not returning any email messages. Not sure how to treat the special chars. Also when the string has " ' " char no emails are returned. Thanks for your help – Vin Oct 22 '20 at 17:21
32

The above answer does not actually work anymore or maybe never did but i modified it so it returns only unseen messages, it used to give : error cannot parse fetch command or something like that here is a working code :

mail = imaplib.IMAP4_SSL('imap.gmail.com')
(retcode, capabilities) = mail.login('email','pass')
mail.list()
mail.select('inbox')

n=0
(retcode, messages) = mail.search(None, '(UNSEEN)')
if retcode == 'OK':

   for num in messages[0].split() :
      print 'Processing '
      n=n+1
      typ, data = mail.fetch(num,'(RFC822)')
      for response_part in data:
         if isinstance(response_part, tuple):
             original = email.message_from_string(response_part[1])

             print original['From']
             print original['Subject']
             typ, data = mail.store(num,'+FLAGS','\\Seen')

print n

I think the error was coming from the messages[0].split(' ') but the above code should work fine.

Also, note the +FLAGS instead of -FLAGS which flags the message as read.

EDIT 2020: If you pass by in 2020 after python 2.7 death: replace email.message_from_string(data[0][1]) with email.message_from_bytes(data[0][1])

Espoir Murhabazi
  • 5,973
  • 5
  • 42
  • 73
Amr El Aswar
  • 3,395
  • 3
  • 23
  • 36
10

You may use imap_tools package: https://pypi.org/project/imap-tools/

from imap_tools import MailBox, AND
with MailBox('imap.mail.com').login('test@mail.com', 'password', 'INBOX') as mailbox:
    # get unseen emails from INBOX folder
    for msg in mailbox.fetch(AND(seen=False)):
        print(msg.date, len(msg.html or msg.text))
Vladimir
  • 6,162
  • 2
  • 32
  • 36
7
original = email.message_from_string(response_part[1])

Needs to be changes to:

original = email.message_from_bytes(response_part[1])
Kara
  • 6,115
  • 16
  • 50
  • 57
  • 1
    This is probably a necessary fix for Python 3, but does not attempt to answer the question of the OP, and should be a comment instead, apparently in reference to [Amro's answer](http://stackoverflow.com/a/29629741/874188). – tripleee Feb 12 '16 at 11:39
3

I've managed to get this to work using Gmail:

import datetime
import email
import imaplib
import mailbox


EMAIL_ACCOUNT = "your@gmail.com"
PASSWORD = "your password"

mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login(EMAIL_ACCOUNT, PASSWORD)
mail.list()
mail.select('inbox')
result, data = mail.uid('search', None, "UNSEEN") # (ALL/UNSEEN)
i = len(data[0].split())

for x in range(i):
    latest_email_uid = data[0].split()[x]
    result, email_data = mail.uid('fetch', latest_email_uid, '(RFC822)')
    # result, email_data = conn.store(num,'-FLAGS','\\Seen') 
    # this might work to set flag to seen, if it doesn't already
    raw_email = email_data[0][1]
    raw_email_string = raw_email.decode('utf-8')
    email_message = email.message_from_string(raw_email_string)

    # Header Details
    date_tuple = email.utils.parsedate_tz(email_message['Date'])
    if date_tuple:
        local_date = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
        local_message_date = "%s" %(str(local_date.strftime("%a, %d %b %Y %H:%M:%S")))
    email_from = str(email.header.make_header(email.header.decode_header(email_message['From'])))
    email_to = str(email.header.make_header(email.header.decode_header(email_message['To'])))
    subject = str(email.header.make_header(email.header.decode_header(email_message['Subject'])))

    # Body details
    for part in email_message.walk():
        if part.get_content_type() == "text/plain":
            body = part.get_payload(decode=True)
            file_name = "email_" + str(x) + ".txt"
            output_file = open(file_name, 'w')
            output_file.write("From: %s\nTo: %s\nDate: %s\nSubject: %s\n\nBody: \n\n%s" %(email_from, email_to,local_message_date, subject, body.decode('utf-8')))
            output_file.close()
        else:
            continue
Edward Chapman
  • 509
  • 6
  • 8
0

I didn't like the existing solutions so I decided to make a sister library for my email sender called Red Box.

Here is an example of how to fetch new messages and process them:

from redbox import EmailBox

# Create email box instance
box = EmailBox(
    host="imap.example.com", 
    port=993,
    username="me@example.com",
    password="<PASSWORD>"
)

# Select an email folder
inbox = box["INBOX"]

# Search and process messages
for msg in inbox.search(unseen=True):

    # Process the message
    print(msg.headers)
    print(msg.from_)
    print(msg.to)
    print(msg.subject)
    print(msg.text_body)
    print(msg.html_body)

    # Set the message as read/seen
    msg.read()

There is also a query language if you need complex logical operations. You can also easily access various parts of the messages if needed.

To install:

pip install redbox

Links:

miksus
  • 2,426
  • 1
  • 18
  • 34