Home Portfolio Electronics Projects Embedded Systems CS Projects IoT & Security Blog About Contact
 Home How to Send Emails Using the Gmail API in Google Colab (With Every Error Fixed)

How to Send Emails Using the Gmail API in Google Colab (With Every Error Fixed)

August 19, 2025 Umayanga 0 comments

How to Send Emails Using the Gmail API in Google Colab (With Every Error Fixed)

A practical, battle-tested guide — including all the errors you'll hit and exactly how to fix them.

Introduction

Sending emails programmatically using Python is incredibly useful — for automation, notifications, project alerts, or hobby projects like IoT devices. Google's Gmail API is one of the most reliable ways to do it, but getting it working inside Google Colab is surprisingly tricky. The official documentation assumes you're running on a local machine with a browser, which Colab doesn't have.

This guide walks you through the entire setup from scratch — Google Cloud Console, OAuth credentials, Python code — and documents every real error you'll encounter along the way, with the exact fix for each one.


What You'll Need

  • A Google account (Gmail)
  • A Google Cloud account (free) at console.cloud.google.com
  • A Google Colab notebook
  • (Optional) A custom domain email added as a Gmail alias

Step 1: Set Up Google Cloud Project

  1. Go to console.cloud.google.com
  2. Click "Select a project""New Project"
  3. Give it a name (e.g., mailsend-testtool) and click Create
Create Google Cloud Project

Step 2: Enable the Gmail API

  1. In your project, go to APIs & Services → Library
  2. Search for "Gmail API"
  3. Click it → Enable

Step 3: Configure the OAuth Consent Screen

  1. Go to APIs & Services → OAuth consent screen
  2. Choose External
  3. Fill in the required fields: App name, support email
  4. Under Scopes, add:
    https://www.googleapis.com/auth/gmail.send
  5. Under Test users, add your Gmail address
  6. Save and continue
Important: Because you added a sensitive scope (gmail.send), Google will mark your app as "unverified." This is completely normal for personal projects — you don't need to submit for verification. More on this below.

Step 4: Create OAuth 2.0 Credentials

  1. Go to APIs & Services → Credentials
  2. Click "+ Create Credentials"OAuth 2.0 Client ID
  3. Application type: Desktop app
  4. Click Create, then Download JSON
  5. This is your credentials.json file — keep it safe.

Also add http://localhost to Authorized redirect URIs in this credential (you'll need this later).


Step 5: Install Required Libraries in Colab

!pip install -q google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client

Step 6: The Working Code

Here is the complete, single-cell solution that works in Colab — with credentials embedded directly (no file upload needed) and the token saved to /tmp/ so it reuses automatically within the same session.

!pip install -q google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client

import base64, os, json
from email.message import EmailMessage
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
from urllib.parse import urlparse, parse_qs

# ============================================================
# 🔧 PASTE YOUR credentials.json CONTENT HERE
# ============================================================
CREDENTIALS_JSON = {
  "installed": {
    "client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
    "project_id": "your-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": "YOUR_CLIENT_SECRET",
    "redirect_uris": ["http://localhost"]
  }
}

# ============================================================
# 📧 YOUR EMAIL ADDRESSES
# ============================================================
GMAIL      = 'you@gmail.com'
DOMAIN_ONE = 'you@yourdomain1.com'   # Custom domain alias 1
DOMAIN_TWO = 'you@yourdomain2.com'   # Custom domain alias 2

# ============================================================
# ⚙️  INTERNAL SETUP
# ============================================================
SCOPES     = ['https://www.googleapis.com/auth/gmail.send']
CREDS_FILE = '/tmp/credentials.json'
TOKEN_FILE = '/tmp/token.json'

# Write credentials dict to temp file
with open(CREDS_FILE, 'w') as f:
    json.dump(CREDENTIALS_JSON, f)


def get_credentials():
    creds = None

    # Load saved token if it exists
    if os.path.exists(TOKEN_FILE):
        creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)

    # Auto-refresh if expired — no browser needed
    if creds and creds.expired and creds.refresh_token:
        creds.refresh(Request())
        with open(TOKEN_FILE, 'w') as f:
            f.write(creds.to_json())
        print("🔄 Token refreshed automatically.")
        return creds

    # First time only — manual auth
    if not creds or not creds.valid:
        flow = InstalledAppFlow.from_client_secrets_file(
            CREDS_FILE, SCOPES,
            redirect_uri='http://localhost'
        )
        auth_url, _ = flow.authorization_url(prompt='consent')
        print("=" * 60)
        print("👉 STEP 1: Open this URL in your browser:\n")
        print(auth_url)
        print("\n" + "=" * 60)
        print("👉 STEP 2: After approving, paste the full localhost URL")
        print("   below (even if the page didn't load — that's expected)\n")

        redirected_url = input("Paste URL here: ")
        code = parse_qs(urlparse(redirected_url).query)['code'][0]
        flow.fetch_token(code=code)
        creds = flow.credentials

        # Save token for reuse within this session
        with open(TOKEN_FILE, 'w') as f:
            f.write(creds.to_json())
        print("\n✅ Token saved! Next run will skip this step.")

    return creds


def send_email(to, subject, body, sender='me'):
    creds = get_credentials()
    service = build('gmail', 'v1', credentials=creds)

    msg = EmailMessage()
    msg['To'] = to
    msg['From'] = sender
    msg['Subject'] = subject
    msg.set_content(body)

    encoded = base64.urlsafe_b64encode(msg.as_bytes()).decode()
    result = service.users().messages().send(
        userId='me',
        body={'raw': encoded}
    ).execute()
    print(f"✅ Sent from [{sender}] → [{to}]")
    print(f"   Message ID: {result['id']}")


# ============================================================
# ✉️  SEND YOUR EMAIL
# ============================================================
send_email(
    to      = 'recipient@example.com',
    subject = 'Hello from Gmail API!',
    body    = 'This was sent via Gmail API from Google Colab.',
    sender  = DOMAIN_ONE    # swap to DOMAIN_TWO or GMAIL as needed
)

Sending from a Custom Domain Email

If you have a custom domain email (like you@yourdomain.com) added to Gmail as an alias, you can send from it directly. Just make sure it's set up in Gmail first:

Gmail → Settings → See all settings → Accounts and Import → Send mail as

Your custom domain email should be listed and verified there. Once it is, just pass it as the sender argument — no extra scopes or configuration needed.

# Send from your main Gmail
send_email(to='friend@example.com', subject='Hi', body='Hello!', sender=GMAIL)

# Send from custom domain 1
send_email(to='friend@example.com', subject='Hi', body='Hello!', sender=DOMAIN_ONE)

# Send from custom domain 2
send_email(to='friend@example.com', subject='Hi', body='Hello!', sender=DOMAIN_TWO)

Errors You'll Hit (And How to Fix Them)

This is the most important section. Here are every real error encountered during setup, in the order you'll face them.

❌ Error 1: could not locate runnable browser

Full error:

Error: could not locate runnable browser

Why it happens: The standard flow.run_local_server(port=0) tries to open a browser window on your machine. Colab runs on a remote server — there is no browser to open.

Fix: Don't use run_local_server() in Colab. Instead, manually generate the auth URL and handle the redirect yourself:

# ❌ This fails in Colab
creds = flow.run_local_server(port=0)

# ✅ This works in Colab
flow = InstalledAppFlow.from_client_secrets_file(
    'credentials.json', SCOPES,
    redirect_uri='http://localhost'
)
auth_url, _ = flow.authorization_url(prompt='consent')
print(auth_url)  # Open this in your browser manually

❌ Error 2: AttributeError: 'InstalledAppFlow' object has no attribute 'run_console'

Full error:

AttributeError: 'InstalledAppFlow' object has no attribute 'run_console'

Why it happens: Many older blog posts and Stack Overflow answers suggest using flow.run_console() as the Colab-friendly alternative to run_local_server(). However, run_console() was removed in newer versions of google-auth-oauthlib. Colab uses a recent version, so the method simply doesn't exist anymore.

Fix: Use the manual URL + redirect approach shown in the working code above. Generate the auth URL, open it in your browser, then capture the authorization code from the redirect URL.

❌ Error 3: Error 400: invalid_request — Access blocked

Full error (shown in browser):

Error 400: invalid_request
Access blocked: [your-app]'s request is invalid

Why it happens: Google deprecated the urn:ietf:wg:oauth:2.0:oob redirect URI in 2022. Many guides still recommend it, but Google now blocks it entirely.

Fix — Two parts:

Part A: Change your redirect URI in the code from oob to http://localhost:

# ❌ Deprecated — Google will block this
redirect_uri='urn:ietf:wg:oauth:2.0:oob'

# ✅ Use this instead
redirect_uri='http://localhost'

Part B: Add http://localhost to your OAuth client's authorized redirect URIs in Google Cloud Console:

  1. Go to APIs & Services → Credentials
  2. Click your OAuth 2.0 Client ID
  3. Under Authorized redirect URIs, click Add URI
  4. Add http://localhost
  5. Click Save

❌ Error 4: "This app isn't verified" warning in browser

What you see:

Google hasn't verified this app. The app is requesting access to sensitive info in your Google Account.

Why it happens: Your app uses a sensitive Gmail scope (gmail.send). Google requires verification for apps that will be used by the general public. Since this is a personal project, you don't need to submit for verification.

Fix:

  1. On the warning screen, click "Advanced"
  2. Click "Go to [your app name] (unsafe)"
  3. Grant permission

This is completely safe for your own personal project. Also make sure your Gmail address is added under Test users on the OAuth consent screen.


⚠️ Note: Token is Lost on Runtime Reset

The token is saved to /tmp/token.json. This folder is wiped whenever your Colab runtime resets (which happens after inactivity or manually). When that occurs, you'll need to go through the browser auth flow one more time.

Fix for persistent token across resets: Mount Google Drive and save the token there instead:

from google.colab import drive
drive.mount('/content/drive')

TOKEN_FILE = '/content/drive/MyDrive/gmail_token.json'

This way the token survives runtime resets and you only ever need to authenticate once.


How the Auth Flow Works (Summary)

Run What happens
First run Auth URL is printed → open in browser → approve → paste the localhost URL → token saved
Same session, next run Token loaded from /tmp/token.json — runs instantly
Token expired Auto-refreshed silently using the refresh token
Runtime reset /tmp/ is cleared → need to auth once more (use Drive to avoid this)

Conclusion

Getting the Gmail API working in Google Colab is more involved than the official docs suggest, mainly because Colab's headless server environment breaks the standard OAuth flow. The key lessons:

  • Don't use run_local_server() or run_console() in Colab
  • The oob redirect URI is deprecated — use http://localhost instead
  • The "unverified app" warning is normal — just click through it for personal use
  • Save your token to Google Drive if you want it to survive runtime resets
  • Sending from a custom domain alias just requires changing the sender field — no extra setup needed

Once it's working, you can reuse the send_email() function anywhere in your Colab notebook for notifications, alerts, or anything else your project needs.