To automate sending of emails, we can write a Python program that will access any mail server. For this tutorial, we will use Google Mail server. But why Google Mail?
Google Mail (also known as GMail) is a highly secured server. Most examples on the Internet on how to send an email with GMail server has cut corners by advising developers to set “Less secure app access” on! This will only make one’s account vulnerable, exposing it to potential risks! Thankfully, on May 30, 2022 Google will disable this feature.
It is a little bit complex to implement Python scripts calling GMail functions, but I was able to have it working. Note that the application I created was tested to work only run on desktop client on a test environment and not yet in Production. I will update this post once I launch the application in Production.
PART 1 – Enabling Gmail API
PART 2 – Setup Python project workspace
PART 3 – Writing the Python Code
PART 4 – Running the Python program for sending an email
PART 5 – Troubleshooting
PART 1 – Enabling Gmail API
Step 1. Follow Steps 1 through 18 on How to Create Google Mail API Credentials JSON file in Google Cloud https://techiejackieblogs.com/how-to-create-google-mail-api-credentials-json/
You should have a JSON file downloaded to your local file system by the end of the above tutorial.
Step 2. Open the client_secret JSON file. This will be the credentials JSON file that will later be used by your Python program. The formatted file should look like this:
{
"installed": {
"client_id": "1000000000000-________________________________.apps.googleusercontent.com",
"project_id": "________",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_secret": "______-____________________________",
"redirect_uris": [
"http://localhost"
]
}
}
Step 3. Go to your project’s API dashboard https://console.cloud.google.com/apis/dashboard. Click the “+ ENABLE APIS AND SERVICES” link.
Step 4. Search for Gmail API. Click the “ENABLE” button.
PART 2 – Setup Python project workspace
Step 1. Install Pycharm Community Edition in https://www.jetbrains.com/pycharm/download/
This tutorial uses the latest stable release pycharm-community-2021.1.2.exe
Step 2. Open Pycharm. Create New Project with a Virtual environment. The latest Pycharm installer will always have new virtual environment installed.
Step 3. Create new file with filename requirements.txt. Enter the following library names
google-api-python-client
google-auth-httplib2
google-auth-oauthlib
Step 4. Select “Terminal” on the bottom panel. This will launch a command line. Execute the command to install the libraries listed in requirements.txt
pip3 install -r requirements.txt
Step 5. Copy the JSON file created from PART 1 – Enabling Gmail API. Rename the file to credentials.json. You should now have 3 files in your project.
PART 3 – Writing the Python script
Write these lines of codes in main.py
import os import pickle from googleapiclient.discovery import build from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from base64 import urlsafe_b64encode from email.mime.text import MIMEText # Request all access (permission to read/send/receive emails, manage the inbox, and more) SCOPES = ['https://mail.google.com/'] DESTINATION_EMAIL_ADDRESS = 'youremail@gmail.com' EMAIL_SUBJECT = 'Testing from Python' EMAIL_BODY = 'This is a test' def gmail_authenticate(): 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) return build('gmail', 'v1', credentials=creds) def build_message(destination, obj, body): message = MIMEText(body, "html") message['to'] = destination message['subject'] = obj return {'raw': urlsafe_b64encode(message.as_bytes()).decode()} def send_message(service, destination, obj, body): return service.users().messages().send( userId="me", body=build_message(destination, obj, body) ).execute() if __name__ == "__main__": service = gmail_authenticate() send_message(service, DESTINATION_EMAIL_ADDRESS, EMAIL_SUBJECT, EMAIL_BODY)
PART 4 – Running the Python Code
Step 1. From Pycharm Terminal, run
python main.py
Step 2. A browser window will open so you can choose which account will be accessing the API. Note that “Gmail TJB” is the project name I created in PART 1.
Step 3. A warning message will be displayed. Google hasn’t verified this app. Verification will be in the final step prior to Production launching. For now, click the “Continue” button.
Step 4. An information on what the API wants to access will be displayed. If you are granting the API access to your email account, click the “Continue” button.
Step 5. This message will be displayed “The authentication flow has completed. You may close this window.” Return to Pycharm project and you will find that file token.pickle has been created after completing the authentication flow. File token.pickle stores the user’s access and refresh tokens
Step 6. Check your mailbox. You should receive the email sent by your Python program.
Note that next time you run the Python program, Steps 2 to 5 will be skipped since you already have token.pickle created in your local file system.
PART 5 – Troubleshooting
Error Message | Cause | Solution | |
#1 | Authorization Error Error 403: access_denied The developer hasn’t given you access to this app. It’s currently being tested and it hasn’t been verified by Google. If you think you should have access, contact the developer (developers_email_address@gmail.com) | The SENDER_EMAIL_ADDRESS is not included in the Test Users list | Go to APIs & Services -> OAuth consent screen . Add the SENDER_EMAIL_ADDRESS into the Test Users list |
#2 | raise HttpError(resp, content, uri=self.uri) googleapiclient.errors.HttpError: HttpError 403 when requesting https://gmail.googleapis.com/gmail/v1/users/me/messages/send?alt=json returned “Gmail API has not been used in project 1000000000000 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/gmail.googleapis.com/overview?project=1000000000000 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.”. Details: “[{‘message’: ‘Gmail API has not been used in project 1000000000000 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/gmail.googleapis.com/overview?project=1000000000000 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.’, ‘domain’: ‘usageLimits’, ‘reason’: ‘accessNotConfigured’, ‘extendedHelp’: ‘https://console.developers.google.com’}]” | Access to Google API has not been enabled yet for the project. | From a web browser, enable the Google API by entering the URL https://console.developers.google.com/apis/api/gmail.googleapis.com/overview?project=1000000000000 The URL is on the Details of the error message. Click the ENABLE button. |
#3 | creds.refresh(Request()) TypeError: init() missing 1 required positional argument: ‘url’ | This happens after running the application for a certain period, the token expires. In my application, token was expiring after an hour. | Delete token.pickle from your file system and re-run the application. |
References:
https://developers.google.com/gmail/api/quickstart/python
https://realpython.com/python-send-email/
https://www.thepythoncode.com/article/use-gmail-api-in-python
https://github.com/x4nth055/pythoncode-tutorials/tree/master/general/gmail-api