Sending Emails with Python on a Static Site: Overview
smtplib is the standard-library Python module that sends email over SMTP (Simple Mail Transfer Protocol), and the Gmail API is Google’s REST interface for sending mail with OAuth2 tokens instead of a stored password. This post uses both to send newsletters from a backend-less GitHub Pages site.
Because this blog is a static website hosted on GitHub Pages (which I wrote about in my AI-Free Website Design post), I do not have a backend server. That means I cannot simply run a PHP script or a Node.js process to handle contact forms or send out newsletters.
But that hasn’t stopped me. I use a hybrid approach: a lightweight service for receiving messages, and pure Python code for sending them.
Receiving Form Submissions on a Static Site with a Form Endpoint
To handle incoming form submissions on a static site, you need an endpoint. For years, I have used UseBasin.com.
The setup is incredibly straightforward: you generate an HTML form on their dashboard, copy the action URL, and paste it into your site’s HTML. You can customise the form styling entirely with your own CSS.
I prefer it over other solutions because it handles spam filtering effectively and allows me to export my subscriber list easily. Once I download the new subscribers as a CSV file to my local machine, I am ready for the fun part: sending the emails with Python!
Sending Email Messages with Python smtplib and the Gmail API
We will cover the necessary steps, including setting up your Gmail account, using Python’s smtplib module, and configuring all the essential security settings in Gmail.
Prerequisites
Before we start, ensure you have the following:
- Python installed on your system. Download it from the official website.
- Gmail Account to utilise Google’s SMTP server.
Security Settings
We can now use the Less Secure App Access and Enhanced Security (OAuth2.0).
The main difference between Enhanced Security (OAuth2.0) and Less Secure App Access for sending emails with Python via Gmail boils down to authentication method and security implications:
| Aspect | Enhanced Security (OAuth2.0) | Less Secure App Access |
|---|---|---|
| Credential sent | Short-lived access token (client ID + secret via Google Cloud Console) | Your Gmail username and password |
| Password exposure | Password never exposed to the application | Full password stored/entered by the app |
| Scope of access | Granular, limited to the requested scope (e.g. gmail.send) |
Full account access |
| Recommended use | Production and any security-sensitive app | Testing only; deprecated by Google in late 2024 |
With OAuth2.0 you create credentials through the Google Cloud Platform, your application requests a temporary access token specific to the intended action (e.g. sending emails), and it then uses that token to authenticate with Gmail’s API. With Less Secure App Access the application enters your Gmail username and password directly against Gmail’s SMTP server, granting full account access.
Please note that as of late 2024, Google requires Enhanced Security (OAuth2) for Google Workspace, and “Less secure app access” has been largely deprecated. Read the historical context in Google Workspace Updates. If you are building a new application today, you should default to OAuth2.
Suppose you are curious about the security of using Python’s e-mail libraries. In that case, I suggest reading the comprehensive article Nothing new, still broken, insecure by default since then: Python’s e-mail libraries and certificate verification.
Email parameters and messages
Firstly, we set up the email credentials, recipient, subject, and body.
MAIL_USERNAME = 'your_email@gmail.com' # your_email
password = 'your_password' # is not required for the OAuth2 method
to_email = 'your_email@gmail.com'
subject = 'Test Email'
body = 'This is a test email sent from Python.'
The email.mime library creates email content in a structured format, allowing for more complex email structures, such as attachments and HTML content.
We use MIMEMultipart to create a message container and attach the email body using MIMEText.
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
message = MIMEMultipart()
message['From'] = MAIL_USERNAME
message['To'] = to_email
message['Subject'] = subject
message.attach(MIMEText(body, 'plain'))
Calling message.attach(MIMEText(body, 'plain')) attaches the body of the email as simple text. However, it is often desirable to send emails in HTML format so you can control the styling.
You can further improve the HTML string with inline CSS that can enable a better design, dark/light mode, images and fonts you prefer,
html_string = f"<html><body style='background: black; color: white;'>{body}</body></html>"
body_html = MIMEText(html_string, "html")
message.attach(body_html)
The email client will try to render the last attached (HTML) part first; it will show the plain text when it fails.
Less Secure App Access
Setting Up Your Gmail Account
To send emails through Gmail’s SMTP server, enable “Less secure app access” in your Gmail account settings. This allows third-party apps to access your account.
To enable “Less Secure Apps” for your Gmail account, you’ll need to disable “Two-Step Verification” first if it is disabled.
To turn on the “Less secure app access”, go to Google Account Security Settings.
Import Necessary Libraries
We import smtplib for sending emails using the Simple Mail Transfer Protocol (SMTP), ssl for creating a secure SSL context, MIMEText and MIMEMultipart from email.mime.text and email.mime.multipart respectively, for creating the email content.
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import ssl
SSL is a security protocol that encrypts communication between a web server and a browser or client, protecting sensitive information such as login credentials and credit card details from unauthorised interception.
Send the Email
Finally, we establish a secure connection with Gmail’s SMTP server using SMTP_SSL, log in, and send the email.
# Create a secure SSL context
context = ssl.create_default_context()
# Send the email using SMTP over SSL
with smtplib.SMTP_SSL("smtp.gmail.com", 465, context=context) as server:
# Log in to the SMTP server
server.login(MAIL_USERNAME, password)
# Send the email
server.sendmail(MAIL_USERNAME, to_email, message.as_string())
We create a secure SSL context to secure the connection to the SMTP server with ssl.create_default_context(). The SSL context ensures that the connection to the SMTP server is secure, protecting your login credentials and email content from being intercepted.
This function uses SMTP over SSL with smtplib.SMTP_SSL(“smtp.gmail.com”, 465, context=context) is used as the server, maintaining a connection to the Gmail SMTP server on port 465.
The function server.login(MAIL_USERNAME, password) logs us into the SMTP server using your Gmail address and password.
Finally, server.sendmail(MAIL_USERNAME, to_email, message.as_string()) sends the email from your Gmail address to the recipient’s email address using the created email message.
Please notice that if you have two-factor authentication enabled on your Google account, you’ll need to generate an app-specific password instead of using your regular Gmail password.
Fixing smtplib.SMTPAuthenticationError: (535, ‘5.7.8 Username and Password not accepted’)
Cause: Gmail rejected the credentials passed to server.login() — usually because “Less secure app access” is disabled (deprecated since late 2024) or because two-factor authentication is on and you used your normal password instead of an app password.
Fix: Generate a 16-character app password at Google App Passwords and pass it as the password value, or switch to the OAuth2 method below, which does not use server.login() at all.
The test e-mail went well for me. Let’s go further with OAuth2 usage.
Using OAuth2.0 for secure authentication
OAuth2.0 enhances security by avoiding the need to store and manage passwords.
Prerequisites
When we explored the Less Secure App Access, we already mentioned the required G-mail account, Python installation, and the smtplib library (included with Python).
For this more secure OAuth2.0-based setup, we have to install the oauth2client and google-auth libraries:
pip install google-auth google-auth-oauthlib google-auth-httplib2
Setting Up Your Gmail Account
To send emails through Gmail’s SMTP server using OAuth2.0, follow these steps:
- Create a Project in Google Cloud Console:
- Go to the Google Cloud Console.
- Create a new project.
- Enable the Gmail API for your project.
- Create OAuth2.0 Credentials:
- Go to the “Credentials” page.
- Create OAuth 2.0 Client IDs and download the
.jsoncredentials file.
Please note that you might need to fill out the OAuth consent screen when you do not have an organisation set up. In that form, you can fill out your app name, user support email, developer contact information, and other relevant fields.
You can choose any permission level that fits your app requirements in the scope screen. For sending e-mails, I have enabled the following:
- Gmail API …/auth/gmail.send Send email on your behalf
You can add more scope elements as needed in your application.
Next, we will have to create an OAuth client ID. I have selected a Desktop app with the name “My blog mailer”. As a result, you will get the message window “OAuth client created
The client ID and secret can always be accessed from Credentials in APIs & Services, where we download our JSON credentials for further use. I have renamed it “client_secret_for_my_blog_mailer.json.”
Import Necessary Libraries
We import necessary libraries, os for checking the existence of the token file, Request from google.auth.transport.requests to refresh credentials, Credentials from google.oauth2.credentials to handle the credentials,InstalledAppFlow from google_auth_oauthlib.flow to manage the OAuth2 flow.
import os
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
For sending emails, we will use base64 to encode the message in a format suitable for the Gmail API, build from googleapiclient.discovery to build the Gmail API service, and HTTPError from requests to handle potential HTTP errors during the API request.
import base64
from googleapiclient.discovery import build
from requests import HTTPError
Define OAuth2.0 Scope and Token File
Set the Gmail API scope and specify paths for the token file TOKEN_FILE and credentials files CREDENTIALS_FILE (usually downloaded from the Google Cloud Console)
Scopes required for accessing the Gmail API. In this case, the scope is set to allow sending emails.
SCOPES = ['https://www.googleapis.com/auth/gmail.send']
TOKEN_FILE = 'token.json'
CREDENTIALS_FILE = 'client_secret_for_my_blog_mailer.json'
Please note that we still need the ‘token.json’ file; we will get it soon.
Function to Get OAuth2.0 Credentials
The function get_oauth2_credentials() handles the OAuth2.0 flow, storing and refreshing tokens as needed.
def get_oauth2_credentials():
"""
Retrieves OAuth2 credentials for accessing the Gmail API.
This function checks if valid credentials are available locally. If not,
it initiates the OAuth2 flow to obtain new credentials and saves them for
future use.
Returns:
Credentials: OAuth2 credentials for the Gmail API.
"""
creds = None
# Check if the token file exists
if os.path.exists(TOKEN_FILE):
# Load the credentials from the token file
creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)
# If no valid credentials are available, initiate the OAuth2 flow
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
# Refresh the credentials if they are expired
creds.refresh(Request())
else:
# If no valid credentials are found, initiate the OAuth2 flow
flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_FILE, SCOPES)
creds = flow.run_local_server(port=0)
# Save the new credentials to the token file for future use
with open(TOKEN_FILE, 'w') as token:
token.write(creds.to_json())
return creds
If the token file exists, we load the credentials from the file. Otherwise, we initiate the OAuth2 flow. We also refresh the credentials using the refresh token. The newly refreshed token is saved into the file ‘TOKEN_FILE’ for future usage.
When calling the function above, your default web browser window will open and request email access. When you select your Gmail account, you will get a message: “The authentication flow has completed. You may close this window.”
Send the Email
We can next use these credentials in the function send_email_with_oauth2(). The service = build('gmail', 'v1', credentials=creds) uses the retrieved credentials to build the Gmail API service for version 1 of the API.
def send_email_with_oauth2():
"""
Sends an email using the Gmail API with OAuth2 authentication.
This function retrieves OAuth2 credentials, builds the Gmail API service,
creates a raw email message, and sends it using the Gmail API.
"""
# Retrieve OAuth2 credentials
creds = get_oauth2_credentials()
# Build the Gmail API service
service = build('gmail', 'v1', credentials=creds)
# Create the raw message object
# The 'message' variable should be defined with the email content
raw_message = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
try:
# Send the email message
sent_message = service.users().messages().send(userId="me", body=raw_message).execute()
print(f'Message {sent_message} was sent')
except HTTPError as error:
# Handle HTTP errors
print(f'Error: {error}')
sent_message = None
send_email_with_oauth2()
We use base64 for creating the raw email message raw_message = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}, which encodes the email message in base64 format. The message variable is an instance of an email message object (e.g., email.mime.text.MIMEText defined before.
Finally, we send the email in the try block using the Gmail API. When catching the HTTPError exceptions, we print an error message and set’ sent_message to None`.
The e-mail was sent well again :)
This approach ensures that the function is self-contained, easy to understand, and handles potential errors gracefully.
Using OAuth2.0 to send emails with Python and Gmail provides a secure way to handle authentication, avoiding the need to manage passwords directly.
For more details, refer to the official Python documentation on smtplib and the Gmail API Overview.
Conclusion: Automating Email from a Backend-Less Python Site
In this post, I have shared the main ideas behind my email messaging setup: capturing subscriptions securely with UseBasin.com, and dispatching newsletters programmatically using Python and Gmail.
Sending emails using Python with smtplib is incredibly satisfying. Once you have the OAuth2 authentication configured, you can automate notifications, error reporting, or even your own bulk email campaigns.
Now it’s your turn to start your email pet project. Best of luck!
Sending Emails with Python FAQ
What is the difference between OAuth2 and ‘Less secure app access’ for sending Gmail from Python?
OAuth2 issues a short-lived access token scoped to a single action (such as gmail.send) and never exposes your password, while ‘Less secure app access’ sends your Gmail username and password directly to the SMTP server. Google deprecated ‘Less secure app access’ in late 2024, so new applications should default to OAuth2.
Which port and method should I use for Gmail SMTP in Python?
Use smtplib.SMTP_SSL("smtp.gmail.com", 465, context=ssl.create_default_context()) for an implicit-SSL connection on port 465. Wrap the send in a with block so the connection closes automatically.
Do I need an app password if two-factor authentication is enabled?
Yes for the smtplib password method — with two-factor authentication enabled you must generate a 16-character app-specific password instead of your normal Gmail password. The OAuth2 method needs no password at all.
Where does the OAuth2 token.json file come from?
It is created automatically the first time you run the OAuth2 flow: run_local_server opens a browser, you grant access, and the resulting credentials are written to token.json. On later runs the token is loaded and refreshed instead of prompting again.
Try the following fantastic AI-powered applications.
I am affiliated with some of them (to support my blogging at no cost to you). I have also tried these apps myself, and I liked them.
10web.io builds a website with AI. You can also host your wesbite on 10Web Hosting, and optimise it with PageSpeed Booster.
B12.io Recently, I have found an AI-powered platform that enables you to create professional websites, pages, posts, and emails with ease. I will also give it a try and soon write a new post about B12.io (I am working on my coding post at the moment :).
Mixo.io generates websites instantly using AI. Builds stunning landing pages without any code or design. Includes a built-in email waiting list and all the tools you need to launch, grow, and test your ideas.
UseBasin.com is a comprehensive backend automation platform for handling submissions, processing, filtering, and routing without coding.
Did you like this post? Please let me know if you have any comments or suggestions.
Python posts that might be interesting for youRelated tools you may want to try next.
B12.io Recently, I have found an AI-powered platform that enables you to create professional websites, pages, posts, and emails with ease. I will also give it a try and soon write a new post about B12.io (I am working on my coding post at the moment :).
References
5. Google Account Security Settings
Enjoyed this? Get more like it.
Weekly notes on AI tools, Python, and what I'm actually building — plus a free copy of Fantastic AI: The 2026 Toolkit.