Google API Gmail LINE Notifications Part 2

Part 2 in a series of articles on implementing a notification system using Gmail and Line Bot.

Gmail Python Line Bot
1. Installation & First Run
2. Labels
3. Getting the Emails
4. Processing Email Contents
5. Processing Email - Being Selective
6. Processing Email - Single Script / Multiple Emails
7. Automation & Code

Hi again. If you followed the guide I referenced in the previous post. A Beginner’s Guide to the Gmail API and Its Documentation. The next section will be somewhat familiar.

In this section we will be accessing the labels used within Gmail, creating a new label, and also getting the new label’s id. The id is critical because the google API does not use the label’s name. It is merely the name displayed in the user interface.

I am going to structure some of the code also at this point.

This will be the new starting code.

import pickle, os.path, sys
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/gmail.modify',
          'https://www.googleapis.com/auth/gmail.labels']

def get_service():
    """Shows basic usage of the Gmail API.
    Lists the user's Gmail labels.
    """
    creds = None
    # The file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)
    # Another option to ignore google cache logging issue
    # service = build('gmail', 'v1', credentials=creds, cache_discovery=False)
    service = build('gmail', 'v1', credentials=creds)
    return service

    
def main():
    service = get_service()

if __name__ == '__main__':
    main()

I have moved the code that will connect to the API into its own function call get_service which returns an object we can use to access the API. The keen reader my have noticed that I have updated the SCOPES. I will be modifying emails in a later post in the series and we also need access to the labels to be able to read and modify them.

The next set of functions will be added to the file just above: def main():

First may want to be able to get a list of all the labels we have in our Gmail account. This can be done using this code.

def get_labels(service):
    list_of_labels = service.users().labels().list(userId='me').execute()
    return list_of_labels.get('labels')

get_labels return a list of json objects. Which are just dictionaries in python.

Next we may want to create a new label to use with Gmail. This can be done with following code.

def define_label(name, mlv="show", llv="labelShow"):
    label = dict()
    label["messageListVisibility"] = mlv
    label["labelListVisibility"] = llv
    label["name"] = name
    return label

This is the simplest json form that will create a valid label in Gmail. If we call this function:

define_label("test")

we get the following json string:

{
  'messageListVisibility': 'show',
  'labelListVisibility': 'labelShow',
  'name': 'test'
}

After creating this object we want to actually add it to Gmail. We can do that using the following code.

def add_label_to_gmail(service, label):
    try:
        created_label = service.users().labels().create(userId='me',
                                                        body=label).execute()
        return created_label
    except Exception as e:
        logger.error(e)

What add_label_to_gmail() does is call the API with a body of json containing the information to create a new label within Gmail. If there is a problem, for example, trying to add a new label when one with the name already exists; we will get an exception.
If everything goes well we will get a new json containing the original json we created, but it will also now contain the label’s id

{
  'id': 'Label_30',
  'name': 'test',
  'messageListVisibility': 'show',
  'labelListVisibility': 'labelShow'
}

We can now get that new id using the next function

def get_new_label_id(new_label):
    return new_label.get('id')

This simply returns label as a string.

In practice this means making the following set of calls.

new_label = define_label("test")
new_label = add_label_to_gmail(service, new_label)
new_id = get_new_label_id(new_label)

But why or how do we use this new id once its been created and added to Gmail?
As mentioned at in the first article. I am working on making a system that queries and sends notifications based on some emails. I have a need to query Gmail fairly frequently during certain times of the day, say once every 5 minutes. Gmail’s search options only allows me to limit my search conditions to emails that are newer_than:1day. That means, I will see the same emails repeatedly triggering multiple notifications.

The fix? Add a label when a message is processed and then use -label:labelname in the search string. Meaning that once an email is processed. It won’t get processed a second time.

How do you add a label to a message?

def add_label_to_message(service, msg_id, label_id):
    try:
        msg = service.users().messages().modify(userId='me',
                                                id=msg_id,
                                                body={'removeLabelIds': [],
                                                      'addLabelIds': [label_id]}
                                                ).execute()
    except Exception as e:
      # Do something here. print or log

This takes the service, a msg_id, and the label_id we got after creating the new label.

Where does the msg_id come from? That is a story for the next post in our series.


See also