From: wayne+blog@waynewerner.com To: everyone.everywhere.all.at.once Date: Fri, 14 Nov 2025 18:06:56 -0600 Subject: Google, OAuth, IMAP, and You!
I love command lines. Did you know that you don't need the Google Gmail UI?
From using MS-DOS, to BBSes, and later, when I learned about Linux and SSH. I
even preferred ftp to some kind of graphical system for uploading my HTML
files to Angelfire. You used to be able to telnet vera.cals.lib.ar.us and
browse the catalog, or login and request holds or renew your library books. I
used the terminal theme in Gmail.
I would dual boot Windows Vista and Ubuntu linux in college, eventually just only running Linux. I baked a few different Raspberry Pi's into cyberdecks.
On our RedHat 7.1 Linux systems at home, I started with Alpine (the email client, not the Linux distro), even going so far as to nowaday interact with Gmail over IMAP, first with app-specific passwords, and now with OAuth.
But... I'm not entirely satisifed with Alpine. It does a lot of things I love
and it's been quite faithful, however I'm evolving some different needs. I can
definitely say that mutt doesn't fill my needs, though it's a pretty cool app
all on its own.
But this isn't about Linux and Alpine. This is about Google APIs and IMAP.
Not that long ago I started messing with the YouTube API and I was able to knock something together that logged in over OAuth and then would create some scheduled streams for you. This worked great!
And I knew that Alpine used OAuth to connect to gmail but I wasn't really sure what it did to get there. At one point I have vague recollections that I used the source to get the client secret (Is that something I was supposed to have???) to communicate with Google's API but I still didn't know what I was doing.
After my experience with the YouTube API, I really wanted to come back and see
if I could connect to my own Gmail account and use Python's IMAP library to
communicate with it. I had used imaplib before but I wasn't entirely certain
that I was going to be doing the right thing.
And it turned out... I wasn't, at least not quite. I was able to do the Google OAuth flow successfully - my client ID was setup as a Desktop app, so if I understand correctly the "client secret" isn't really secret. Just called that because it's easier than completely rebranding.
Since I had the flow sorted, it was a matter of finding the right API to put everything together. Also, some examples base64 encoded the AUTHORIZE request before sending it to Gmail. That was actually a red herring.
Here are a bunch of sources that I tried but were all found completely useless or dead ends:
google api python "userinfo"userinfogoogle-auth and google-auth-oauthlib)This is where I finally found the answer:
https://github.com/googleapis/google-api-python-client/blob/main/docs/oauth.md
Putting it together, here's a very basic example of something that works for
uinsg Python to connect to Gmail over IMAP, using OAuth2, and nothing
hard-coded in the app. All you need is your client secrets in a
client_secret.json file:
'''
Usage:
uv run imap-gmail.py
'''
# /// script
# dependencies = ['google-auth', 'google-auth-oauthlib', 'pudb', 'google-api-python-client']
# ///
from imaplib import IMAP4_SSL
import json
import base64
from google_auth_oauthlib.flow import InstalledAppFlow
import google.oauth2.credentials as gcreds
from googleapiclient.discovery import build
try:
with open('credentials.json') as f:
data = json.load(f)
except (FileNotFoundError, json.decoder.JSONDecodeError):
data = {}
try:
creds = gcreds.Credentials.from_authorized_user_info(data)
except (ValueError, AttributeError):
flow = InstalledAppFlow.from_client_secrets_file(
"client_secret.json",
scopes=[
"openid",
"https://www.googleapis.com/auth/gmail.metadata",
"https://mail.google.com/",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/gmail.readonly",
"https://www.googleapis.com/auth/gmail.modify",
],
)
creds = flow.run_local_server(port=0)
with open('credentials.json', 'w') as f:
f.write(creds.to_json())
user_info_service = build('oauth2', 'v2', credentials=creds)
user_info = user_info_service.userinfo().get().execute()
obj = f"user={user_info['email']}\1auth=Bearer {creds.token}\1\1"
with IMAP4_SSL('imap.gmail.com') as imap:
imap.debug = 4
print(imap.authenticate('XOAUTH2', lambda x: obj))
imap.select('INBOX')
typ, data = imap.search(None, 'ALL')
for num in data[0].split():
print(num)
Save that as imap-gmail.py, download/create your client_secret.json from the Google
Cloud console, and run uv run imap-gmail.py. You'll get an OAuth gmail login screen, and then you'll see a
list of email numbers from your INBOX.
Extending this to something useful is left as an exercise for the reader.
~Wayne