Reading Gmail with Raspberry Pi and IMAPClient
Sep 16, 2017
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.
- Log into your gmail account
- Navigate to the Sign-in and security page
- Under theĀ Signing in to Google section, click theĀ 2-Step Verification menu, then follow the instructions to enable 2-Step Verification
- Back in the Sign-in and security page right under theĀ 2-Step Verification button you'll seeĀ App passwords.
- Generate a password
- 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 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+X
,Ā thenĀ 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 f****eed 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.
Comments
Leave a Comment
Bas Rossen
April 12, 2019 at 5:11:04 PM UTC
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?
Skyler Harrison
November 14, 2019 at 11:27:37 AM UTC
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?
Sam Storino
November 15, 2019 at 5:31:17 PM UTC
Hey Skyler ā the emails are marked UNREAD, correct? If you send me the scripts you wrote I can take a peek to see if anything sticks out.
Jasen
December 27, 2019 at 2:58:33 PM UTC
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?
John Windle
March 2, 2021 at 9:27:18 AM UTC
Iām having the same problem with Authentication Failed. I enabled āLess Secure Appsā in gmail, but I still get the same error
John Windle
March 2, 2021 at 10:01:23 AM UTC
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!