I’ve carried out Google’s instructions to set up a Google Cloud Platform project which enables GMail API; and I’m able to send out emails using the generated credentials file provided by the Google Cloud Platform. The credentials file is used for both Python and NodeJs.

017-feature.png
GMail API: quick start with Python and NodeJs.

Sending emails via GMail from applications with just a GMail email and a password will not be supported after May, 30th 2022; see Less secure app access: https://myaccount.google.com/lesssecureapps; and this will not work with accounts that have “2-Step Verification” enabled. GMail API should be used to send emails. And I have been able to do it successfully. This post is a documentation of what I have done.

For this post, I’m running test codes in a Python virtualenv; please see https://behainguyen.wordpress.com/2022/02/15/python-virtual-environment-virtualenv-for-multiple-python-versions/; for how to set up a Python virtualenv.

1. Configure a Google Cloud Platform project

Google’s API Python Quickstart: https://developers.google.com/gmail/api/quickstart/python provides enough information on how to set up a Google Cloud Platform project with GMail API enabled. I have done a similar configuration for YouTube Data API before, but still it took me two attempts to get this GMail API configuration right.

Basically, these are the steps we have to do:

  • Create a Google Cloud Platform project.

  • “Enabled APIs & services”; i.e. GMail API.

  • Configure “OAuth Consent Screen”. Note that, on screen, the sidebar on the left has “Credentials” before “OAuth consent screen”: if we run “Credentials” first, it will take us back to “OAuth consent screen”, I did this in my second attempt. Not a major problem, just a few more clicks.

  • Create “OAuth client ID” credentials. This was where I got it wrong the first time: I selected “Service account” instead.

  • Download credentials file as JSON. This is the secret credentials file that applications need to use. I downloaded the generated credentials file as client_secret_oauth_gmail.json.

The images below are step by step of the process above:

2. Testing Python codes

2.a. Google sample code

As per Google’s API Python Quickstart: https://developers.google.com/gmail/api/quickstart/python I’m using Python 3.10.1, so that should be okay; and we need to install Google client libraries:

pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

The Python codes from the aforementioned Google link:

File: quickstart.py
from __future__ import print_function

import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

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

def main():
    """Shows basic usage of the Gmail API.
    Lists the user's Gmail labels.
    """
    creds = None
    # The file token.json 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.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # 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(
                'client_secret_oauth_gmail.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    try:
        # Call the Gmail API
        service = build('gmail', 'v1', credentials=creds)
        results = service.users().labels().list(userId='me').execute()
        labels = results.get('labels', [])

        if not labels:
            print('No labels found.')
            return
        print('Labels:')
        for label in labels:
            print(label['name'])

    except HttpError as error:
        # TODO(developer) - Handle errors from gmail API.
        print(f'An error occurred: {error}')


if __name__ == '__main__':
    main()

My command to run it:

(venv310) F:\Codes\Python\virtual>.\venv310\Scripts\python.exe quickstart.py

If everything has gone okay, the default browser will display GMail account selection, just accept and continue: ignore warnings as we’re testing our own stuff. Finally, the last screen appears with the following message:

The authentication flow has completed. You may close this window.

The output looks like the following screen:

017-gmail-api-23.png
quickstart.py’s output.

2.b. Sending emails code

Using the above aforementioned Google link, together with the following Stackoverflow posts, I managed to put together a workable quick and dirty test code email-03.py.

File: email-03.py

Before running, please delete token.json; which was written out during previous runs.

from __future__ import print_function

import os.path

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

import base64

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

def CreateMessageHtml(sender, to, subject, msgHtml, msgPlain):
    msg = MIMEMultipart('alternative')
    msg['Subject'] = subject
    msg['From'] = sender
    msg['To'] = to
    msg.attach(MIMEText(msgPlain, 'plain'))
    msg.attach(MIMEText(msgHtml, 'html'))
    message = { 'raw' : base64.urlsafe_b64encode(msg.as_bytes()).decode("utf-8") }
    return message

def SendMessageInternal(service, user_id, message):
    try:
        message = (service.users().messages().send(userId=user_id, body=message).execute())
        print('Message Id: %s' % message['id'])
        return message
    except HttpError as error:
        print('An error occurred: %s' % error)
        return "Error"
    return "OK"

def main():
    """Shows basic usage of the Gmail API.
    Lists the user's Gmail labels.
    """
    creds = None
    # The file token.json 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.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # 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(
                'client_secret_oauth_gmail.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())

    try:
        # Call the Gmail API
        service = build('gmail', 'v1', credentials=creds)

        to = "behai_nguyen@hotmail.com"
        sender = "Email_Used_In_Google_Cloud_Platform_Project@gmail.com"
        subject = "Message via GMail API"
        msgHtml = "<h1>Hello, this email is send from GMail using GMail API OAuth.</h1>"
        msgPlain = "Hello, this email is send from GMail using GMail API OAuth."

        message1 = CreateMessageHtml(sender, to, subject, msgHtml, msgPlain)
        result = SendMessageInternal(service, "me", message1)

        print( result )

    except HttpError as error:
        # TODO(developer) - Handle errors from gmail API.
        print(f'An error occurred: {error}')


if __name__ == '__main__':
    main()

The sent email as seen in my Hotmail account:

017-gmail-api-24.png
The sent email in destination mailbox.

3. Testing NodeJs code

Please note, I’m using NodeJs v10.15.3.

This Google’s API Node.js quickstart: https://developers.google.com/gmail/api/quickstart/nodejs is NodeJs equivalent of the Python’s one, also using the same generated credentials JSON file.

I just followed the instructions, installing required package. Save the code as quickstart.js, and run it with the command:

   "C:\Program Files\nodejs\node.exe" quickstart.js

There are two additional steps involved. At the command prompt, it writes out a link, and we have to copy this link, and paste into the browser in which the account used for the Google Cloud Platform project is signed in, it then redirects to a link similar to the below:

http://localhost/?code=4/0AX4XfWgY5FRSgl4Gm0ogQLu0MzSFV4vc2sy6S3Omu6llMNrtxiNjk4RkxdAXtemH8nXvWQ&scope=https://www.googleapis.com/auth/gmail.readonly

We need to copy out the value of the code query param and give it to the command prompt’s awaiting prompt. The output is similar to the Python’s equivalent:

017-gmail-api-nodejs-25.png
quickstart.js’ output.

I’ve not worked on NodeJs sending email codes yet… I’ll get to it later on. Thank you for visiting and I do hope you find this useful.