Send and receive Gmail via the Gmail API using Python

Introduction

As you know, Gmail has Gmail API, which allows you to take advantage of Gmail's unique features such as search function without using SMTP or POP3. In this article, we'll walk you step-by-step through the steps from enabling the Gmail API to creating and running scripts using Python.

Before using the Gmail API, creating a project, enabling the API, setting the scope, creating authentication information, etc. are relatively difficult to prepare, so I wrote it as a memorandum.

Preparation before use

Create a project that uses the Google API

Open the Google Cloud Platform console (https://console.cloud.google.com/) in the browser that opened Gmail for your Gmail account.

The following screen will appear. Agree to the terms and conditions, and click "Agree and execute".

A00.jpg

Select "New Project" from "Select Project" A01.jpg

Set the project name and click "Create" A02.jpg

If you can create it, the following screen will be displayed A03.jpg

Enable Gmail API

Click "Library" A04.jpg

Search for "Gmail API" from "Search APIs and Services" A05.jpg

Click the displayed "Gmail API" A06.jpg

Click "Enable" A07.jpg

Create an OAuth consent screen

Click "OAuth consent screen" from "API and services" A08.jpg

Select "External" and click "Create" A09.jpg

Set an appropriate name for "Application Name" and click "Add Scope" A10.jpg

Select the scope as shown below and click "Add" A11.jpg

Click "Save" A12.jpg

Create credentials

Select "Credentials", click "Create Credentials", and click "Select with Wizard" from the menu that appears. A13.jpg

On the "Add Credentials to Project" screen, set as shown below and click "Required Credentials". A14.jpg

Click "Create OAuth Client ID" A15.jpg

Download the credentials (client_id.json) and click "Finish" A16.jpg

When you press the Finish button, the screen will change to the one shown below. A17.jpg

The downloaded credentials file client_id.json will be used later in the Python script.

Supplement: About authentication information

You can create an API key, OAuth2.0 client ID, or service account key with your credentials. When dealing with user data, it seems common to use an OAuth2.0 client ID. The Google Cloud Document Authentication Overview (https://cloud.google.com/docs/authentication?hl=ja) page describes the uses of each authentication method.

The expiration date of the credential Refresh Token is described below. Once authenticated and the Refresh Token is issued, it does not seem to expire unless it is left for 6 months or 50 or more tokens are issued. It seems that automation mechanisms such as RPA are also acceptable. https://developers.google.com/identity/protocols/OAuth2#expiration

You must write your code to anticipate the possibility that a granted refresh token might no longer work. A refresh token might stop working for one of these reasons:

* The user has revoked your app's access.
* The refresh token has not been used for six months.
* The user changed passwords and the refresh token contains Gmail scopes.
* The user account has exceeded a maximum number of granted (live) refresh tokens.
* There is currently a limit of 50 refresh tokens per user account per client. If the limit is reached, creating a new refresh token automatically invalidates the oldest refresh token without warning. This limit does not apply to service accounts.

Python script preparation

Now that we're ready, we'll have a Python script to run.

Preparing the Python environment

(I think many people have already lived in this part)

Python installation

Install according to here. Please install 3.7.x.

Installation of pipenv

Follow the steps here (http://pipenv-ja.readthedocs.io/ja/translate-ja/install.html#installing-pipenv) to install.

Creating a python script

Prepare a suitable directory and prepare a python script. Save the client_id.json saved in the previous step in the same directory. Joined the Gmail API samples: sweat_drops:

You can find sample code in the Gmail API reference. It's easy to use when you come here!

gmail_credential.py



import os
import pickle
from google.auth.transport.requests import Request
from google_auth_oauthlib.flow import InstalledAppFlow


#Set scope for Gmail API
SCOPES = [
    "https://www.googleapis.com/auth/gmail.compose",
    "https://www.googleapis.com/auth/gmail.readonly",
    "https://www.googleapis.com/auth/gmail.labels",
    "https://www.googleapis.com/auth/gmail.modify",
]


def get_credential():
    """
Obtaining an access token

Save the token in pickle format in the current directory so that it can be reused. (Sorry for the miscellaneous ...)
    """
    creds = None
    if os.path.exists("token.pickle"):
        with open("token.pickle", "rb") as token:
            creds = pickle.load(token)
    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("client_id.json", SCOPES)
            # creds = flow.run_local_server()
            creds = flow.run_console()
        with open("token.pickle", "wb") as token:
            pickle.dump(creds, token)
    return creds

listmail.py



"""
list GMail Inbox.

Usage:
  listmail.py <query> <tag> <count>
  listmail.py -h | --help
  listmail.py --version

Options:
  -h --help     Show this screen.
  --version     Show version.
"""
import pickle
import base64
import json
import io
import csv
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import base64
from email.mime.text import MIMEText
from apiclient import errors
import logging
from docopt import docopt
from gmail_credential import get_credential

logger = logging.getLogger(__name__)


def list_labels(service, user_id):
    """
Get a list of labels
    """
    labels = []
    response = service.users().labels().list(userId=user_id).execute()
    return response["labels"]


def decode_base64url_data(data):
    """
Base64url decoding
    """
    decoded_bytes = base64.urlsafe_b64decode(data)
    decoded_message = decoded_bytes.decode("UTF-8")
    return decoded_message


def list_message(service, user_id, query, label_ids=[], count=3):
    """
Get a list of emails

    Parameters
    ----------
    service : googleapiclient.discovery.Resource
Resources for communicating with Gmail
    user_id : str
User ID
    query : str
Email query string. is:unread etc.
    label_ids : list
List of IDs indicating the label to be searched
    count : str
Maximum number of email information to be returned

    Returns
    ----------
    messages : list
        id, body, subject,List of dictionary data with keys such as from
    """
    messages = []
    try:
        message_ids = (
            service.users()
            .messages()
            .list(userId=user_id, maxResults=count, q=query, labelIds=label_ids)
            .execute()
        )

        if message_ids["resultSizeEstimate"] == 0:
            logger.warning("no result data!")
            return []

        #Check the content of the message based on the message id
        for message_id in message_ids["messages"]:
            message_detail = (
                service.users()
                .messages()
                .get(userId="me", id=message_id["id"])
                .execute()
            )
            message = {}
            message["id"] = message_id["id"]
            message["body"] = decode_base64url_data(
                message_detail["payload"]["body"]["data"]
            )
            # payload.headers[name: "Subject"]
            message["subject"] = [
                header["value"]
                for header in message_detail["payload"]["headers"]
                if header["name"] == "Subject"
            ][0]
            # payload.headers[name: "From"]
            message["from"] = [
                header["value"]
                for header in message_detail["payload"]["headers"]
                if header["name"] == "From"
            ][0]
            logger.info(message_detail["snippet"])
            messages.append(message)
        return messages

    except errors.HttpError as error:
        print("An error occurred: %s" % error)


def remove_labels(service, user_id, messages, remove_labels):
    """
Delete the label. Used to mark as read(is:If you remove the unread label, it becomes read)
    """
    message_ids = [message["id"] for message in messages]
    labels_mod = {
        "ids": message_ids,
        "removeLabelIds": remove_labels,
        "addLabelIds": [],
    }
    # import pdb;pdb.set_trace()
    try:
        message_ids = (
            service.users()
            .messages()
            .batchModify(userId=user_id, body=labels_mod)
            .execute()
        )
    except errors.HttpError as error:
        print("An error occurred: %s" % error)


#Main processing
def main(query="is:unread", tag="daily_report", count=3):
    creds = get_credential()
    service = build("gmail", "v1", credentials=creds, cache_discovery=False)
    #Label list
    labels = list_labels(service, "me")
    target_label_ids = [label["id"] for label in labels if label["name"] == tag]
    #Email list[{'body': 'xxx', 'subject': 'xxx', 'from': 'xxx'},]
    messages = list_message(service, "me", query, target_label_ids, count=count)
    # unread label
    unread_label_ids = [label["id"] for label in labels if label["name"] == "UNREAD"]
    # remove labels form messages
    remove_labels(service, "me", messages, remove_labels=unread_label_ids)
    logger.info(json.dumps(messages, ensure_ascii=False))
    if messages:
        return json.dumps(messages, ensure_ascii=False)
    else:
        return None


#Program execution part
if __name__ == "__main__":
    arguments = docopt(__doc__, version="0.1")
    query = arguments["<query>"]
    tag = arguments["<tag>"]
    count = arguments["<count>"]
    logging.basicConfig(level=logging.DEBUG)

    messages_ = main(query=query, tag=tag, count=count)
    print(messages_)

The send script looks like this.

sendmail.py


"""
Send E-Mail with GMail.

Usage:
  sendmail.py <sender> <to> <subject> <message_text_file_path>  [--attach_file_path=<file_path>] [--cc=<cc>]
  sendmail.py -h | --help
  sendmail.py --version

Options:
  -h --help     Show this screen.
  --version     Show version. 
  --attach_file_path=<file_path>     Path of file attached to message.
  --cc=<cc>     cc email address list(separated by ','). Default None.
"""
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import base64
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.audio import MIMEAudio
from pathlib import Path

from email.mime.multipart import MIMEMultipart
import mimetypes
from apiclient import errors
from gmail_credential import get_credential
from docopt import docopt
import logging

logger = logging.getLogger(__name__)


def create_message(sender, to, subject, message_text, cc=None):
    """
Base64 encode MIMEText
    """
    enc = "utf-8"
    message = MIMEText(message_text.encode(enc), _charset=enc)
    message["to"] = to
    message["from"] = sender
    message["subject"] = subject
    if cc:
        message["Cc"] = cc
    encode_message = base64.urlsafe_b64encode(message.as_bytes())
    return {"raw": encode_message.decode()}


def create_message_with_attachment(
    sender, to, subject, message_text, file_path, cc=None
):
    """
Base64 encode MIMEText with attachments
    """
    message = MIMEMultipart()
    message["to"] = to
    message["from"] = sender
    message["subject"] = subject
    if cc:
        message["Cc"] = cc
    # attach message text
    enc = "utf-8"
    msg = MIMEText(message_text.encode(enc), _charset=enc)
    message.attach(msg)

    content_type, encoding = mimetypes.guess_type(file_path)

    if content_type is None or encoding is not None:
        content_type = "application/octet-stream"
    main_type, sub_type = content_type.split("/", 1)
    if main_type == "text":
        with open(file_path, "rb") as fp:
            msg = MIMEText(fp.read(), _subtype=sub_type)
    elif main_type == "image":
        with open(file_path, "rb") as fp:
            msg = MIMEImage(fp.read(), _subtype=sub_type)
    elif main_type == "audio":
        with open(file_path, "rb") as fp:
            msg = MIMEAudio(fp.read(), _subtype=sub_type)
    else:
        with open(file_path, "rb") as fp:
            msg = MIMEBase(main_type, sub_type)
            msg.set_payload(fp.read())
    p = Path(file_path)
    msg.add_header("Content-Disposition", "attachment", filename=p.name)
    message.attach(msg)

    encode_message = base64.urlsafe_b64encode(message.as_bytes())
    return {"raw": encode_message.decode()}


def send_message(service, user_id, message):
    """
send an email

    Parameters
    ----------
    service : googleapiclient.discovery.Resource
Resources for communicating with Gmail
    user_id : str
User ID
    message : dict
        "raw"Key,A dict with a base64-encoded MIME Object as value

    Returns
    ----------
None
    """
    try:
        sent_message = (
            service.users().messages().send(userId=user_id, body=message).execute()
        )
        logger.info("Message Id: %s" % sent_message["id"])
        return None
    except errors.HttpError as error:
        logger.info("An error occurred: %s" % error)
        raise error


#Main processing
def main(sender, to, subject, message_text, attach_file_path, cc=None):
    #Obtaining access tokens and building services
    creds = get_credential()
    service = build("gmail", "v1", credentials=creds, cache_discovery=False)
    if attach_file_path:
        #Creating email body
        message = create_message_with_attachment(
            sender, to, subject, message_text, attach_file_path, cc=cc
        )
    else:
        message = create_message(
            sender, to, subject, message_text, cc=cc
        )
    #send e-mail
    send_message(service, "me", message)


#Program execution part
if __name__ == "__main__":
    arguments = docopt(__doc__, version="0.1")
    sender = arguments["<sender>"]
    to = arguments["<to>"]
    cc = arguments["--cc"]
    subject = arguments["<subject>"]
    message_text_file_path = arguments["<message_text_file_path>"]
    attach_file_path = arguments["--attach_file_path"]

    logging.basicConfig(level=logging.DEBUG)

    with open(message_text_file_path, "r", encoding="utf-8") as fp:
        message_text = fp.read()

    main(
        sender=sender,
        to=to,
        subject=subject,
        message_text=message_text,
        attach_file_path=attach_file_path,
        cc=cc,
    )

Preparing to run a python script

Go to the directory where you created the python script and install the required modules.

% pipenv install google-api-python-client oauth2client google-auth-httplib2 google-auth-oauthlib docopt

Run python script

% pipenv run python listmail.py is:unread Request a quote 10

If you execute such as, the OAuth authentication screen URL will be displayed on the screen of cmd.exe at the first execution, so open it from the browser and approve it. From the second time onward, the OAuth authentication screen URL will not be displayed.

% pipenv run python listmail.py is:unread Request a quote 10
Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=AAAAAAAAAAAAAA
Enter the authorization code:

When you open the URL displayed on the above console, the following screen will be displayed. A18.jpg

Click "Details" and click the link "Go to XX (unsafe page)" that appears. A19.jpg

The permission grant dialog will be displayed several times, so allow each. A20.jpg

At the end, a confirmation screen will be displayed, so allow it. A21.jpg

Copy the code from the screen below and paste it after ```Enter the authorization code:` `` to run the script. A22.jpg

at the end

The Gmail API is interesting because you can use Gmail's powerful filters, labeling features and queries as-is.

Recommended Posts

Send and receive Gmail via the Gmail API using Python
Send using Python with Gmail
To automatically send an email with an attachment using the Gmail API in Python
[Python] Create API to send Gmail
Send email via gmail with Python 3.4.3.
Try using the Wunderlist API in Python
How to get followers and followers from python using the Mastodon API
Tweet using the Twitter API in Python
Send and receive image data as JSON over the network with Python
Send and receive binary data via serial communication with python3 (on mac)
Try using the BitFlyer Ligntning API in Python
Send Gmail at the end of the process [Python]
Get Gmail subject and body with Python and Gmail API
Try using ChatWork API and Qiita API in Python
Try using the DropBox Core API in Python
Send Gmail in Python
Receive the form in Python and do various things
Initial settings when using the foursquare API in python
PHP and Python samples that hit the ChatWork API
Speech transcription procedure using Python and Google Cloud Speech API
Using the National Diet Library Search API in Python
A little bit from Python using the Jenkins API
Try using the Twitter API
Try using the Twitter API
Try using the PeeringDB 2.0 API
Send and receive Flask images
Call the API with python3.
Try hitting the Twitter API quickly and easily with Python
Predict gender from name using Gender API and Pykakasi in Python
[Python] I tried collecting data using the API of wikipedia
Tweet Now Playing to Twitter using the Spotify API. [Python]
Get the weather using the API and let the Raspberry Pi speak!
I made a Chatbot using LINE Messaging API and Python
Aggregate and analyze product prices using Rakuten Product Search API [Python]
Send and receive data with MQTT via Watson IoT Platform
[aws] Send and receive sqs messages
The story of Python and the story of NaN
Getting the arXiv API in Python
Data acquisition using python googlemap api
Hit the Sesami API in Python
[Python] Hit the Google Translation API
[September 2020 version] Explains the procedure to use Gmail API with Python
[Python3] Google translate google translate without using api
Send messages and images using LineNotify
Try using Pleasant's API (python / FastAPI)
Cooperation between python module and API
Hit the Etherpad-lite API with Python
Collect product information and process data using Rakuten product search API [Python]
Create Gmail in Python without API
Hit the web API in Python
Clustering and visualization using Python and CytoScape
Use the Flickr API from Python
Get and set the value of the dropdown menu using Python and Selenium
[Python] Conversation using OpenJTalk and Talk API (up to voice output)
Extract the targz file using python
Try using Python argparse's action API
Try using the Python Cmd module
Regularly upload files to Google Drive using the Google Drive API in Python
Run Ansible from Python using API
Access the Twitter API in Python
[Python] I wrote a REST API using AWS API Gateway and Lambda.