python imaplib to get gmail inbox subjects titles and sender name - python
I'm using pythons imaplib to connect to my gmail account. I want to retrieve the top 15 messages (unread or read, it doesn't matter) and display just the subjects and sender name (or address) but don't know how to display the contents of the inbox.
Here is my code so far (successful connection)
import imaplib
mail = imaplib.IMAP4_SSL('imap.gmail.com')
mail.login('mygmail#gmail.com', 'somecrazypassword')
mail.list()
mail.select('inbox')
#need to add some stuff in here
mail.logout()
I believe this should be simple enough, I'm just not familiar enough with the commands for the imaplib library. Any help would be must appreciated...
UPDATE
thanks to Julian I can iterate through each message and retrieve the entire contents with:
typ, data = mail.search(None, 'ALL')
for num in data[0].split():
typ, data = mail.fetch(num, '(RFC822)')
print 'Message %s\n%s\n' % (num, data[0][1])
mail.close()
but I'm wanting just the subject and the sender. Is there a imaplib command for these items or will I have to parse the entire contents of data[0][1] for the text: Subject, and Sender?
UPDATE
OK, got the subject and sender part working but the iteration (1, 15) is done by desc order apparently showing me the oldest messages first. How can I change this? I tried doing this:
for i in range( len(data[0])-15, len(data[0]) ):
print data
but that just gives me None for all 15 iterations... any ideas? I've also tried mail.sort('REVERSE DATE', 'UTF-8', 'ALL') but gmail doesnt support the .sort() function
UPDATE
Figured out a way to do it:
#....^other code is the same as above except need to import email module
mail.select('inbox')
typ, data = mail.search(None, 'ALL')
ids = data[0]
id_list = ids.split()
#get the most recent email id
latest_email_id = int( id_list[-1] )
#iterate through 15 messages in decending order starting with latest_email_id
#the '-1' dictates reverse looping order
for i in range( latest_email_id, latest_email_id-15, -1 ):
typ, data = mail.fetch( i, '(RFC822)' )
for response_part in data:
if isinstance(response_part, tuple):
msg = email.message_from_string(response_part[1])
varSubject = msg['subject']
varFrom = msg['from']
#remove the brackets around the sender email address
varFrom = varFrom.replace('<', '')
varFrom = varFrom.replace('>', '')
#add ellipsis (...) if subject length is greater than 35 characters
if len( varSubject ) > 35:
varSubject = varSubject[0:32] + '...'
print '[' + varFrom.split()[-1] + '] ' + varSubject
this gives me the most recent 15 message subject and sender address in decending order as requested! Thanks to all who helped!
c.select('INBOX', readonly=True)
for i in range(1, 30):
typ, msg_data = c.fetch(str(i), '(RFC822)')
for response_part in msg_data:
if isinstance(response_part, tuple):
msg = email.message_from_string(response_part[1])
for header in [ 'subject', 'to', 'from' ]:
print '%-8s: %s' % (header.upper(), msg[header])
This should give you an idea on how to retrieve the subject and from?
This was my solution to get the useful bits of information from emails:
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
For those looking for how to check mail and parse the headers, this is what I used:
def parse_header(str_after, checkli_name, mailbox) :
#typ, data = m.search(None,'SENTON', str_after)
print mailbox
m.SELECT(mailbox)
date = (datetime.date.today() - datetime.timedelta(1)).strftime("%d-%b-%Y")
#date = (datetime.date.today().strftime("%d-%b-%Y"))
#date = "23-Jul-2012"
print date
result, data = m.uid('search', None, '(SENTON %s)' % date)
print data
doneli = []
for latest_email_uid in data[0].split():
print latest_email_uid
result, data = m.uid('fetch', latest_email_uid, '(RFC822)')
raw_email = data[0][1]
import email
email_message = email.message_from_string(raw_email)
print email_message['To']
print email_message['Subject']
print email.utils.parseaddr(email_message['From'])
print email_message.items() # print all headers
I was looking for a ready made simple script to list last inbox via IMAP without sorting through all messages. The information here is useful, though DIY and misses some aspects. First, IMAP4.select returns message count. Second, subject header decoding isn't straightforward.
#! /usr/bin/env python
# -*- coding: utf-8 -*-
import imaplib
import email
from email.header import decode_header
import HTMLParser
# to unescape xml entities
_parser = HTMLParser.HTMLParser()
def decodeHeader(value):
if value.startswith('"=?'):
value = value.replace('"', '')
value, encoding = decode_header(value)[0]
if encoding:
value = value.decode(encoding)
return _parser.unescape(value)
def listLastInbox(top = 4):
mailbox = imaplib.IMAP4_SSL('imap.gmail.com')
mailbox.login('mygmail#gmail.com', 'somecrazypassword')
selected = mailbox.select('INBOX')
assert selected[0] == 'OK'
messageCount = int(selected[1][0])
for i in range(messageCount, messageCount - top, -1):
reponse = mailbox.fetch(str(i), '(RFC822)')[1]
for part in reponse:
if isinstance(part, tuple):
message = email.message_from_string(part[1])
yield {h: decodeHeader(message[h]) for h in ('subject', 'from', 'date')}
mailbox.logout()
if __name__ == '__main__':
for message in listLastInbox():
print '-' * 40
for h, v in message.items():
print u'{0:8s}: {1}'.format(h.upper(), v)
BODY gets almost everything and marks the message as read.
BODY[<parts>] gets just those parts.
BODY.PEEK[<parts>] gets the same parts, but doesn't mark the message read.
<parts> can be HEADER or TEXT or HEADER.FIELDS (<list of fields>) or
HEADER.FIELDS.NOT (<list of fields>)
This is what I use: typ, data = connection.fetch(message_num_s, b'(BODY.PEEK[HEADER.FIELDS (SUBJECT FROM)])')
`
def safe_encode(seq):
if seq not in (list,tuple):
seq = [seq]
for i in seq:
if isinstance(i, (int,float)):
yield str(i).encode()
elif isinstance(i, str):
yield i.encode()
elif isinstance(i, bytes):
yield i
else:
raise ValueError
def fetch_fields(connection, message_num, field_s):
"""Fetch just the fields we care about. Parse them into a dict"""
if isinstance(field_s, (list,tuple)):
field_s = b' '.join(safe_encode(field_s))
else:
field_s = tuple(safe_encode(field_s))[0]
message_num = tuple(safe_encode(message_num))[0]
typ, data = connection.fetch(message_num, b'(BODY.PEEK[HEADER.FIELDS (%s)])'%(field_s.upper()))
if typ != 'OK':
return typ, data #change this to an exception if you'd rather
items={}
lastkey = None
for line in data[0][1].splitlines():
if b':' in line:
lastkey, value = line.strip().split(b':', 1)
lastkey = lastkey.capitalize()
#not all servers capitalize the same, and some just leave it
#as however it arrived from some other mail server.
items[lastkey]=value
else:
#subject was so long it ran onto the next line, luckily it didn't have a ':' in it so its easy to recognize.
items[lastkey]+=line
#print(items[lastkey])
return typ, items
`
You drop it into your code example: by replacing the call to 'mail.fetch()' with fetch_fields(mail, i, 'SUBJECT FROM') or fetch_fields(mail, i, ('SUBJECT' 'FROM'))
Adding to all the above answers.
import imaplib
import base64
import os
import email
if __name__ == '__main__':
email_user = "email#domain.com"
email_pass = "********"
mail = imaplib.IMAP4_SSL("hostname", 993)
mail.login(email_user, email_pass)
mail.select()
type, data = mail.search(None, 'ALL')
mail_ids = data[0].decode('utf-8')
id_list = mail_ids.split()
mail.select('INBOX', readonly=True)
for i in id_list:
typ, msg_data = mail.fetch(str(i), '(RFC822)')
for response_part in msg_data:
if isinstance(response_part, tuple):
msg = email.message_from_bytes(response_part[1])
print(msg['from']+"\t"+msg['subject'])
This will give you the email's from and subject name.
Related
How would I use imaplib to check if an email is received from a certain email address and then trigger a selenium action?
My code so far import base64 email_user = input('Email: ') email_pass = input('Password: ') M = imaplib.IMAP4_SSL('imap.gmail.com', 993) M.login(email_user, email_pass) M.select() typ, message_numbers = M.search(None, 'ALL') # change variable name, and use new name in for loop for num in message_numbers[0].split(): typ, data = M.fetch(num, '(RFC822)') # num1 = base64.b64decode(num) # unnecessary, I think print(data) # check what you've actually got. That will help with the next line data1 = base64.b64decode(data[0][1]) print('Message %s\n%s\n' % (num, data1)) M.close() M.logout() My code currently prints out all of my emails It prints out a load of text, but how would I refine it to see if an email from an email address is received and then trigger opening a website.
Trying to make a Python code to print E-mail onto a website with IMAP, localhost doesn't respond
So I've been trying to make a code to print my Gmail inbox onto a website. I want to further develop this to only contain certain data from the email and write it onto a database. However, it does not seem to me that there is anything wrong with the code, but the localhost:8080 (The port I'm using) does not load at all. The browser has the loading icon when trying to access the page, but it does not load, even after hours. Command line does not respond with any errors. I also have the GMAIL imap settings correctly, and I have tried it with Outlooks email as well. Here is the code: import webapp2 import smtplib import time import imaplib import email class ReadMail(webapp2.RequestHandler): def get(self): mail = imaplib.IMAP4('xxx#gmail.com',993) mail.login('email#gmail.com','password') type, data = mail.search(None, 'ALL') mail_ids = data[0] id_list = mail_ids.split() first_email_id = int(id_list[0]) latest_email_id = int(id_list[-1]) for i in range(latest_email_id,first_email_id, -1): typ, data = mail.fetch(i, '(RFC822)' ) for response_part in data: if isinstance(response_part, tuple): msg = email.message_from_string(response_part[1]) email_subject = msg['subject'] email_from = msg['from'] self.response.headers["Content-Type"] = "text/plain" self.response.write("From:" + email_from) self.response.write("Subject:" + email_subject) routes = [('/', ReadMail),] app = webapp2.WSGIApplication(routes, debug=True) App.yml is correctly setup as well. This code works with something really simple, such as only containing a print "this". Hopefully someone can help with my problem, thanks in advance!
So after a while I got it working by making my own WSGI application file instead of using the webapp2. There are still some issues such as the message being formatted wrong, but this is my code now: from pyramid.config import Configurator from pyramid.response import Response import email, getpass, imaplib, os, re import sys detach_dir = "C:\OTHERS\CS\PYTHONPROJECTS" def imaptest(request): m = imaplib.IMAP4_SSL("imap.gmail.com") m.login("testi.protokolla#gmail.com", "testiprotokolla221") m.select("INBOX") resp, items = m.search(None, '(FROM "vallu.toivonen96#gmail.com")') items = items [0].split() my_msg = [] msg_cnt = 0 break_ = False for emailid in items[::1]: resp, data = m.fetch(emailid, "(RFC822)") if ( break_ ): break for response_part in data: if isinstance(response_part, tuple): msg = email.message_from_string(response_part[1]) varSubject = msg['subject'] varDate = msg['date'] if varSubject[0] == '$': r, d = m.fetch(emailid, "(UID BODY[TEXT])") ymd = email.utils.parsedate(varDate)[0:3] my_msg.append([ email.message_from_string(d[0][1]), ymd]) msg_cnt += 1 # Print as HTML return Response( 'Content-Type': 'text/html' "Your latest Email:" + str(msg) ) config = Configurator() config.add_route('imaptest', '/imaptest') config.add_view(imaptest, route_name='imaptest') app = config.make_wsgi_app()
Not able to read my gmail inbox using the imap python module
Below is the code that is supposed to read my Gmail account. import smtplib import time import imaplib import email from reportlab.pdfgen import canvas ORG_EMAIL = '#gmail.com' FROM_EMAIL = 'amitesh.sahay#gmail.com' FROM_PWD = '<my_password>' SMTP_SERVER = 'imap.gmail.com' SMTP_PORT = 993 # ------------------------------------------------- # # Utility to read email from Gmail Using Python # # ------------------------------------------------ def read_email_from_gmail(): try: mail = imaplib.IMAP4_SSL(SMTP_SERVER) mail.login(FROM_EMAIL,FROM_PWD) mail.select('inbox') type1, data = mail.search(None, 'ALL') mail_ids = data[0] id_list = mail_ids.split() first_email_id = int(id_list[0]) latest_email_id = int(id_list[-1]) for i in range(latest_email_id,first_email_id, -1): type1, data = mail.fetch(i, '(RFC822)' ) for response_part in data: if isinstance(response_part, tuple): msg = email.message_from_string(response_part[1]) email_subject = msg['subject'] email_from = msg['from'] print('From : ' + email_from + '\n') print('Subject : ' + email_subject + '\n') except ValueError: print("Oops! That was no valid mailbox. Try again...") When I execute the code, it runs without throwing any error, but it's not giving any output as well. I am using python 3.6 and Installed all the python package that comes inbuilt within PyCharm. Is there anything that somebody can point out which could be a probable issue? Please let me know if any further details are required.
Python IMAP4 Don't Mark messages as read
I have a Python script to move messages with a certain subject. The messages get marked as read and I don't want them to be marked as read. What part of the script marks them as read and how do I make it not mark as read? Also, I'm not too sure what I am doing yet, so if there is any redundant code or errors please let me know. import getpass from Crypto.Hash import MD5 import sys import imaplib import email import re password = getpass.getpass() match = "redacted" username = "redacted" dest = "000" pattern_uid = re.compile('\d+ \(UID (?P<uid>\d+)\)') def md5(message): hash = MD5.new() hash.update(message) return hash.hexdigest() md5 = md5(password) if md5 == match: pass else: print "Mismatch" sys.exit() M = imaplib.IMAP4_SSL("mail.redacted.com", 993) M.login(username, password) M.select() typ, data = M.search(None, 'ALL') M.select('Inbox') msgs = M.search(None, 'ALL')[1] num_messages = len(msgs[0].split()) num_messages += 1 def parse_uid(data): match = pattern_uid.match(data) return match.group('uid') for i in range(1, num_messages): try: typ, msg_data = M.fetch(str(i), '(RFC822)') except: pass for response_part in msg_data: if isinstance(response_part, tuple): UID = M.fetch(str(i),'UID') UID = UID[1] try: UID = parse_uid(UID[0]) except: pass msg = email.message_from_string(response_part[1]) for header in [ 'subject' ]: if msg[header] == "Redacted": result = M.uid('COPY', UID, dest) if result[0] == 'OK': mov, data = M.uid('STORE', UID, '+FLAGS', '(\Deleted)') M.expunge() M.close() M.logout()
typ, msg_data = M.fetch(str(i), '(RFC822)') Fetching a message body marks it as read. You'll want to use BODY.PEEK[]. Although, I don't know why you're fetching the whole message just to copy it. Why don't you just fetch the headers? Use BODY.PEEK[HEADERS].
Checking email with Python
I am interested to trigger a certain action upon receiving an email from specific address with specific subject. In order to be able to do so I need to implement monitoring of my mailbox, checking every incoming mail (in particular, i use gmail). what is the easiest way to do that?
Gmail provides the ability to connect over POP, which you can turn on in the gmail settings panel. Python can make connections over POP pretty easily: import poplib from email import parser pop_conn = poplib.POP3_SSL('pop.gmail.com') pop_conn.user('username') pop_conn.pass_('password') #Get messages from server: messages = [pop_conn.retr(i) for i in range(1, len(pop_conn.list()[1]) + 1)] # Concat message pieces: messages = ["\n".join(mssg[1]) for mssg in messages] #Parse message intom an email object: messages = [parser.Parser().parsestr(mssg) for mssg in messages] for message in messages: print message['subject'] pop_conn.quit() You would just need to run this script as a cron job. Not sure what platform you're on so YMMV as to how that's done.
Gmail provides an atom feed for new email messages. You should be able to monitor this by authenticating with py cURL (or some other net library) and pulling down the feed. Making a GET request for each new message should mark it as read, so you won't have to keep track of which emails you've read.
While not Python-specific, I've always loved procmail wherever I could install it...! Just use as some of your action lines for conditions of your choice | pathtoyourscript (vertical bar AKA pipe followed by the script you want to execute in those cases) and your mail gets piped, under the conditions of your choice, to the script of your choice, for it to do whatever it wants -- hard to think of a more general approach to "trigger actions of your choice upon receipt of mails that meet your specific conditions!! Of course there are no limits to how many conditions you can check, how many action lines a single condition can trigger (just enclose all the action lines you want in { } braces), etc, etc.
People seem to be pumped up about Lamson: https://github.com/zedshaw/lamson It's an SMTP server written entirely in Python. I'm sure you could leverage that to do everything you need - just forward the gmail messages to that SMTP server and then do what you will. However, I think it's probably easiest to do the ATOM feed recommendation above. EDIT: Lamson has been abandoned
I found a pretty good snippet when I wanted to do this same thing (and the example uses gmail). Also check out the google search results on this.
I recently solved this problem by using procmail and python Read the documentation for procmail. You can tell it to send all incoming email to a python script like this in a special procmail config file :0: | ./scripts/ppm_processor.py Python has an "email" package available that can do anything you could possibly want to do with email. Read up on the following ones.... from email.generator import Generator from email import Message from email.MIMEBase import MIMEBase from email.MIMEText import MIMEText from email.mime.multipart import MIMEMultipart
https://developers.google.com/gmail/gmail_inbox_feed Says you have to have a corporate Gmail, but I have come to find that you can read Gmail free versions without issues. I use this code to get my blood pressure results I email or text to a gmail address. from email.header import decode_header from datetime import datetime import os import pandas as pd import plotly.graph_objs as go import plotly now = datetime.now() dt_string = now.strftime("%Y.%m.%d %H:%M:%S") print("date_time:", dt_string) email_account = '13123#gmail.com' email_password = '131231231231231231312313F' email_server = 'imap.gmail.com' email_port = 993 accept_emails_from = {'j1231312#gmail.com', '1312312#chase.com', '13131231313131#msg.fi.google.com'} verbose = True def get_emails(): email_number = 0 local_csv_data = '' t_date = None t_date = None t_systolic = None t_diastolic = None t_pulse = None t_weight = None try: mail = imaplib.IMAP4_SSL(email_server) email_code, email_auth_status = mail.login(email_account, email_password) if verbose: print('[DEBUG] email_code: ', email_code) print('[DEBUG] email_auth_status: ', email_auth_status) mail.list() mail.select('inbox') # (email_code, messages) = mail.search(None, 'ALL') (email_code, messages) = mail.search(None, '(UNSEEN)') # only get unread emails to process. subject = None email_from = None for email_id in messages[0].split(): email_number += 1 email_code, email_data = mail.fetch(email_id, '(RFC822)') for response in email_data: if isinstance(response, tuple): # we only want the tuple ,the bytes is just b . msg = email.message_from_bytes(response[1]) content_type = msg.get_content_type() subject, encoding = decode_header(msg["Subject"])[0] subject = str(subject.replace("\r\n", "")) if isinstance(subject, bytes): subject = subject.decode(encoding) email_from, encoding = decode_header(msg.get("From"))[0] if isinstance(email_from, bytes): email_from = email_from.decode(encoding) if content_type == "text/plain": body = msg.get_payload(decode=True).decode() parse_data = body else: parse_data = subject if '>' in email_from: email_from = email_from.lower().split('<')[1].split('>')[0] if email_from in accept_emails_from: parse_data = parse_data.replace(',', ' ') key = 0 for value in parse_data.split(' '): if key == 0: t_date = value t_date = t_date.replace('-', '.') if key == 1: t_time = value if ':' not in t_time: numbers = list(t_time) t_time = numbers[0] + numbers[1] + ':' + numbers[2] + numbers[3] if key == 2: t_systolic = value if key == 3: t_diastolic = value if key == 4: t_pulse = value if key == 5: t_weight = value key += 1 t_eval = t_date + ' ' + t_time if verbose: print() print('--------------------------------------------------------------------------------') print('[DEBUG] t_eval:'.ljust(30), t_eval) date_stamp = datetime.strptime(t_eval, '%Y.%m.%d %H:%M') if verbose: print('[DEBUG] date_stamp:'.ljust(30), date_stamp) print('[DEBUG] t_systolic:'.ljust(30), t_systolic) print('[DEBUG] t_diastolic:'.ljust(30), t_diastolic) print('[DEBUG] t_pulse:'.ljust(30), t_pulse) print('[DEBUG] t_weight:'.ljust(30), t_weight) new_data = str(date_stamp) + ',' + \ t_systolic + ',' + \ t_diastolic + ',' + \ t_pulse + ',' + \ t_weight + '\n' local_csv_data += new_data except Exception as e: traceback.print_exc() print(str(e)) return False, email_number, local_csv_data return True, email_number, local_csv_data def update_csv(local_data): """ updates csv and sorts it if there is changes made. """ uniq_rows = 0 if os.name == 'posix': file_path = '/home/blood_pressure_results.txt' elif os.name == 'nt': file_path = '\\\\uncpath\\blood_pressure_results.txt' else: print('[ERROR] os not supported:'.ljust(30), os.name) exit(911) if verbose: print('[DEBUG] file_path:'.ljust(30), file_path) column_names = ['00DateTime', 'Systolic', 'Diastolic', 'Pulse', 'Weight'] if not os.path.exists(file_path): with open(file_path, 'w') as file: for col in column_names: file.write(col + ',') file.write('\n') # append the new data to file. with open(file_path, 'a+') as file: file.write(local_data) # sort the file. df = pd.read_csv(file_path, usecols=column_names) df_sorted = df.sort_values(by=["00DateTime"], ascending=True) df_sorted.to_csv(file_path, index=False) # remove duplicates. file_contents = '' with open(file_path, 'r') as file: for row in file: if row not in file_contents: uniq_rows += 1 print('Adding: '.ljust(30), row, end='') file_contents += row else: print('Duplicate:'.ljust(30), row, end='') with open(file_path, 'w') as file: file.write(file_contents) return uniq_rows # run the main code to get emails. status, emails, my_data = get_emails() print('status:'.ljust(30), status) print('emails:'.ljust(30), emails) # if the new emails received then sort the files. csv_rows = update_csv(my_data) print('csv_rows:'.ljust(30), csv_rows) exit(0)