Gmail OAuth Setup
Headless-friendly OAuth flow for Gmail API access using gog CLI.
Prerequisites
gogCLI installed (brew install steipete/tap/gogcli)- Google Cloud project with OAuth credentials (Desktop app type)
- Gmail API enabled in the project
Quick Setup
1. Create Google Cloud Project & Credentials
- Go to https://console.cloud.google.com
- Create a new project (or select existing)
- Enable Gmail API: APIs & Services → Library → search "Gmail API" → Enable
- Configure OAuth consent screen: APIs & Services → OAuth consent screen
- Choose "External" user type
- Fill in app name, user support email
- Add scopes:
gmail.modify(or others as needed) - Important: Click "PUBLISH APP" for permanent tokens (see Troubleshooting)
- Create credentials: APIs & Services → Credentials → Create Credentials → OAuth client ID
- Application type: Desktop app
- Download the JSON file
2. Configure gog
gog auth credentials /path/to/client_secret.json
gog auth keyring file # Use file-based keyring for headless
export GOG_KEYRING_PASSWORD="your-password" # Add to .bashrc
3. Run Auth Flow
Run scripts/gmail-auth.sh interactively, or:
# Generate URL
scripts/gmail-auth.sh --url
# User opens URL, approves, copies code from localhost redirect
# Exchange code (do this quickly - codes expire in minutes!)
scripts/gmail-auth.sh --exchange CODE EMAIL
4. Verify
gog gmail search 'is:unread' --max 5 --account [email protected]
Troubleshooting
"Access blocked: [app] has not completed the Google verification process"
Cause: App is in "Testing" mode and the Gmail account isn't a test user.
Solutions (choose one): 1. Publish the app (recommended): - Google Cloud Console → APIs & Services → OAuth consent screen - Click "PUBLISH APP" → Confirm - No Google review needed for personal use - Tokens become permanent
- Add test user:
- OAuth consent screen → Test users → + ADD USERS
- Add the Gmail address you're authorizing
- Tokens still expire in 7 days
"Google hasn't verified this app" warning screen
This is normal for personal apps. Click: 1. Advanced (bottom left) 2. Go to app name
Safe to proceed since you own the app.
Token expires in 7 days
Cause: App is in "Testing" mode.
Fix: Publish the app (see above). Published apps get permanent refresh tokens.
"invalid_request" or "invalid_grant" errors
Causes: - Authorization code expired (they only last a few minutes) - Code was already used - Redirect URI mismatch
Fix: Generate a fresh auth URL and complete the flow quickly. Paste the code immediately after getting it.
"redirect_uri_mismatch" error
Cause: The redirect URI in the token exchange doesn't match what was used in the auth URL.
Fix: This script uses http://localhost. Make sure both the auth URL and exchange use the same redirect URI.
Page hangs after approving permissions (mobile)
Cause: Browser trying to connect to localhost which doesn't exist on phone.
Fix:
- Use a desktop browser instead
- Or tap the address bar while it's "hanging" - the URL contains the code
- The URL will look like: http://localhost/?code=4/0ABC...
Multiple permission checkboxes causing hangs
Cause: Too many OAuth scopes requested.
Fix: Use minimal scopes. gmail.modify alone is usually sufficient and shows just one permission.
Can't find project in Google Cloud Console
Cause: Signed into wrong Google account.
Fix: Check which account owns the project: - Click profile icon (top right) - Switch accounts - Check project dropdown for each account
"invalid_request" with oob redirect (new projects)
Cause: Google deprecated urn:ietf:wg:oauth:2.0:oob for OAuth clients created after 2022.
Fix: Use http://localhost redirect instead (this script's default). After approval, browser redirects to localhost with code in URL.
Scopes Reference
| Scope | Access |
|---|---|
gmail.modify |
Read, send, delete, manage labels (recommended) |
gmail.readonly |
Read only |
gmail.send |
Send only |
gmail.compose |
Create drafts, send |
Files
scripts/gmail-auth.sh— Interactive auth helper
Tips
- Publish your app — Avoids test user limits and 7-day token expiry
- Exchange codes quickly — They expire in minutes
- Use desktop browser — Mobile browsers can be finicky with localhost redirects
- One scope is enough —
gmail.modifycovers most use cases