Reading Gmail with Raspberry Pi and IMAPClient

Reading Gmail with Raspberry Pi and IMAPClient

In part one we covered the basics of what we’re trying to build, and what you’ll need to get started. Here in part two we’ll be playing with some python code to interact with our Gmail account using IMAPClient. Later on, part three is focused on the servo motor, while part four connects it all to the food dispenser.

The goal of this part?

  • Setup your Gmail Account with 2-step authentication for a secure way to interact with it through code. 
  • Write some code to interrogate your inbox for an email and mark it as read when found using the IMAPClient library.

Let’s get to it!

Preparing Your Gmail Account with 2-step Authentication

This is not strictly necessary, but a good security practice. If you don’t mind your credentials being in plain text in your code, feel free to skip ahead.

Since we’re going to be using email as the trigger, we’ll need a way to securely log into our Gmail account from code. You could store your credentials in plain text within the code we write, but I strongly discourage that. Instead, make use of Gmail’s two-step authentication and app passwords

I created a new Gmail account for this purpose.

  1. Log into your gmail account
  2. Navigate to the Sign-in and security page
  3. Under the Signing in to Google section, click the 2-Step Verification menu, then follow the instructions to enable 2-Step Verification
  4. Back in the Sign-in and security page right under the 2-Step Verification button you’ll see App passwords
  5. Generate a password
    1. Note: this password will be 16 digits with spaces separating every 4 digits (e.g. xxxx xxxx xxxx xxxx). Please be sure to include the spaces!

The password you generated can be used to log into your Gmail account. We don’t need it right this second, but we’ll be using it to scan for an email that demands we feed the cats!

Writing Code to Read Your Gmail

Now that we have a Gmail account ready to rock, let’s write some code to interrogate it. We’re going to be using the IMAPClient python library for this purpose, and we’ll wrap the calls to this client in our own Python class.

Install IMAPClient now from the terminal: sudo pip install imapclient

Create the GmailWrapper.py Script

Now let’s create our Gmail wrapper class: nano GmailWrapper.py. Within the GmailWrapper.py script, add the following code:

#!/usr/bin/env python

from imapclient import IMAPClient, SEEN

SEEN_FLAG = 'SEEN'
UNSEEN_FLAG = 'UNSEEN'

class GmailWrapper:
    def __init__(self, host, userName, password):
        #   force the user to pass along username and password to log in as 
        self.host = host
        self.userName = userName
        self.password = password
        self.login()

    def login(self):
        print('Logging in as ' + self.userName)
        server = IMAPClient(self.host, use_uid=True, ssl=True)
        server.login(self.userName, self.password)
        self.server = server

    #   The IMAPClient search returns a list of Id's that match the given criteria.
    #   An Id in this case identifies a specific email
    def getIdsBySubject(self, subject, unreadOnly=True, folder='INBOX'):
        #   search within the specified folder, e.g. Inbox
        self.setFolder(folder)  

        #   build the search criteria (e.g. unread emails with the given subject)
        self.searchCriteria = [UNSEEN_FLAG, 'SUBJECT', subject]

        if(unreadOnly == False):
            #   force the search to include "read" emails too
            self.searchCriteria.append(SEEN_FLAG)

        #   conduct the search and return the resulting Ids
        return self.server.search(self.searchCriteria)

    def markAsRead(self, mailIds, folder='INBOX'):
        self.setFolder(folder)
        self.server.set_flags(mailIds, [SEEN])

    def setFolder(self, folder):
        self.server.select_folder(folder)

Save and exit the nano editor: CTRL+Xthen Y, then Enter.

Verifying the GmailWrapper.py Class Works

Let’s do a test: our script is going to log into the Gmail account, search for email with a specific subject, then mark it as read. Before running the code, send yourself an email with the subject feed cats (the search is case-insensitive, by the way).

We’re going to use the Python interpreter to run our tests. In your terminal make sure you’re in the same directory as the GmailWrapper.py class we just created, then:

# press enter after each line for the interpreter to engage

# invoke the interpreter
python

# import our wrapper class for reference
from GmailWrapper import GmailWrapper

# create an instance of the class, which will also log us in
# the <password> should be the 2-step auth App Password, or your regular password
gmailWrapper = GmailWrapper('imap.gmail.com', '<your gmail username>', '<password>')

# search for any unread emails with the subject 'feed cats', and return their Ids
ids = gmailWrapper.getIdsBySubject('feed cats')

# have the interpreter print the ids variable so you know you've got something
ids

# now lets mark the email as read
gmailWrapper.markAsRead(ids)

# exit the interpreter
quit()

If everything went as planned your email should now be marked as read. Pretty neat!

Conclusion and Next Steps

Part one set the goals and outline. Now with part two wrapped up we have the ability to discern whether or not an email exists. With this power we can trigger certain events, such as spinning a servo motor. That’s exactly what we’re going to do in part three, I’ll meet you there.

Later on in part four we’ll be attaching the Pi and servo motor to the dispenser, but before we make anything permanent let’s get that servo spinning.

6 Replies to “Reading Gmail with Raspberry Pi and IMAPClient”

  1. Hi Sam,

    Thank you for this great post.
    I get stuck with the gmailwapper
    there is no more 2 factor authentication, or its called difrently nowadays
    I added logging in bij Phone after logging is what should be 2 factor authentication ass well
    But after that it is not possible to get to app passwords.
    so i skipped that part.

    After working trough the codes i check if the phyton can log me in but i get the error
    NameError: name ‘GmailWrapper’ is not defined
    What dit i do wrong?

  2. Hey Sam, Fantastic project! I have a question about the gmailwrapper py script. Using the python interpreter for testing, I am able to get connected to my gmail account, however, when I try and search for any type of string in the subject e.g feed cats, nothing gets returned. ids returns nothing, print(ids) returns ‘none’. Any thoughts?

  3. Hey Sam, Thank you for the awesome tutorial and inspiration. This is my first attempt at a Python project thanks to a Raspberry Pi Christmas gift. I’ve followed every step thus far with the exception of having two step verification for the cats gmail account. I receive this error message when attempting to Python Interpreter:

    Logging in as
    Traceback (most recent call last):
    File “C:\Users\Jasen\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\LocalCache\local-packages\Python37\site-packages\imapclient\imapclient.py”, line 342, in login
    unpack=True,
    File “C:\Users\Jasen\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\LocalCache\local-packages\Python37\site-packages\imapclient\imapclient.py”, line 1538, in _command_and_check
    typ, data = meth(*args)
    File “C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.7_3.7.1776.0_x64__qbz5n2kfra8p0\lib\imaplib.py”, line 598, in login
    raise self.error(dat[-1])
    imaplib.error: b'[AUTHENTICATIONFAILED] Invalid credentials (Failure)’

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File “”, line 1, in
    File “C:\Users\Jasen\Desktop\My Stuff\Programming\Thonny\GmailWrapper.py”, line 14, in __init__
    self.login()
    File “C:\Users\Jasen\Desktop\My Stuff\Programming\Thonny\GmailWrapper.py”, line 19, in login
    server.login(self.userName, self.password)
    File “C:\Users\Jasen\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.7_qbz5n2kfra8p0\LocalCache\local-packages\Python37\site-packages\imapclient\imapclient.py”, line 345, in login
    raise exceptions.LoginError(str(e))
    imapclient.exceptions.LoginError: b'[AUTHENTICATIONFAILED] Invalid credentials (Failure)’

    Any ideas?

    1. I’m having the same problem with Authentication Failed. I enabled ‘Less Secure Apps’ in gmail, but I still get the same error

  4. Found the problem: 1.thought I had enabled ‘less secure apps’ but it didn’t take. fixed that. 2.Also needed to enable IMAP forwarding in my google account. Works now!

I'd love to hear your thoughts