Home | Blag Index


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?

Google, OAuth, IMAP, and You!

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:

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

^C


Home | Blag Index

This site is Copyleft Wayne Werner - BY-NC-SA 4.0