61

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?

Asclepius
  • 57,944
  • 17
  • 167
  • 143
Sasha
  • 5,783
  • 8
  • 33
  • 37

7 Answers7

83

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.

mazelife
  • 2,069
  • 17
  • 12
  • 1
    Thank you very much!!! It works great. The only problem I have now is that I need to retrieve only new messages. Of course it is possible to retrieve all messages and then compare the old list with the new one. However I am wondering if there is a better way to do that, if there were a built in method for that. – Sasha Aug 05 '09 at 15:47
  • 2
    In POP mode, gmail will only let you retrieve a message once. Subsequent connections will not show you the message. Unless one of 2 things is happening: do you have "recent mode" turned on? See here: http://mail.google.com/support/bin/answer.py?answer=47948 on how to disable that. Alternatively, if gmail is actually giving you the same email each time, it may be because the quit() method is not getting called on your pop connection. Is there an exception perhaps that's being raised but not caught before calling quit? Then gmail will give you the same mail. – mazelife Aug 05 '09 at 17:22
  • 5
    To make this code work in Python3, add "import email" and change these two lines: messages = [b"\n".join(mssg[1]) for mssg in messages] ;; messages = [email.message_from_bytes(mssg) for mssg in messages] – FvD Jan 11 '17 at 17:14
  • This did not worked, I got: `error_proto: b'-ERR [AUTH] Web login required: https://support.google.com/mail/answer/78754'` – tumbleweed May 10 '17 at 18:48
  • 2
    @tumbleweed you should get an email from google linking you to: https://myaccount.google.com/lesssecureapps. This must be enabled for this to work. – Andrew Dean Jun 27 '17 at 10:53
  • @tumbleweed Generate an in-app password. https://support.google.com/accounts/answer/185833 – Joshua Wolff Dec 21 '19 at 07:48
22

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.

Dana the Sane
  • 14,762
  • 8
  • 58
  • 80
  • Any idea how to send the right information to be authorized? – PascalVKooten Jul 16 '16 at 08:07
  • @PascalvKooten late answer, but the format is https://:@mail.google.com/mail/feed/atom/ If two-factor authentication is enabled you should create an app-specific password: https://support.google.com/accounts/answer/185833?hl=nl – Whitebird Apr 24 '17 at 12:06
  • 1
    @Whitebird It still sounds worth investigating. I guess my interest of this is asynchronous; now that I have an answer I still might mess around with it ^_^ Thanks! – PascalVKooten Apr 24 '17 at 13:04
8

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.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • Procmail really won't solve the problem of triggering an action (unless the action in question is sending a message). – J. Peterson Aug 05 '09 at 04:20
  • 5
    @J. Peterson, you ARE kidding, right?! `| pathtoyourscript` and you can start your script of choice and pipe your mail to it when your favorite conditions are satisfied, and the script can do ANYTHING it wants -- how is that NOT "triggering an action"?! – Alex Martelli Aug 05 '09 at 05:24
5

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

Adam Nelson
  • 7,932
  • 11
  • 44
  • 64
  • salmon is a fork, trying to reach python 3 - it is much more recently touched - https://github.com/moggers87/salmon – user2692263 Aug 02 '17 at 07:02
2

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.

Kredns
  • 36,461
  • 52
  • 152
  • 203
0

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
0

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)

Bryan
  • 11
  • 3
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 29 '22 at 18:47