Bulk Remove Old Gmail Messages Using Python API

Issue with Gmail Storage Cleanup

I hit my Gmail storage limit and need to clean up old messages. The web interface only lets me remove about 100 messages at once which takes forever.

I’m trying to build a Python script using Gmail API to remove messages older than 2 weeks. I set up OAuth in Google Cloud Console but getting permission errors.

from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import pickle
import os
from datetime import datetime, timedelta
import logging

logging.basicConfig(level=logging.INFO)

API_SCOPES = ['https://www.googleapis.com/auth/gmail.modify']

def authenticate_gmail():
    credentials = None
    if os.path.exists('auth_token.pickle'):
        with open('auth_token.pickle', 'rb') as file:
            credentials = pickle.load(file)
    
    if not credentials or not credentials.valid:
        if credentials and credentials.expired and credentials.refresh_token:
            credentials.refresh(Request())
        else:
            auth_flow = InstalledAppFlow.from_client_secrets_file(
                'client_secrets.json', API_SCOPES)
            credentials = auth_flow.run_local_server(port=0)
        
        with open('auth_token.pickle', 'wb') as file:
            pickle.dump(credentials, file)
    
    return credentials

def check_permissions(credentials):
    if credentials and credentials.scopes:
        logging.info(f"Available scopes: {credentials.scopes}")
        needed_scopes = set(API_SCOPES)
        current_scopes = set(credentials.scopes)
        
        if needed_scopes.issubset(current_scopes):
            logging.info("Permission check passed")
        else:
            missing = needed_scopes - current_scopes
            logging.error(f"Missing permissions: {missing}")

def cleanup_old_messages(gmail_service, test_mode=False):
    try:
        cutoff_date = (datetime.now() - timedelta(days=14)).strftime('%Y/%m/%d')
        search_query = f'in:inbox before:{cutoff_date}'
        
        all_messages = []
        result = gmail_service.users().messages().list(userId='me', q=search_query).execute()
        
        if 'messages' in result:
            all_messages.extend(result['messages'])
        
        while 'nextPageToken' in result:
            result = gmail_service.users().messages().list(
                userId='me', q=search_query, pageToken=result['nextPageToken']).execute()
            all_messages.extend(result['messages'])
        
        logging.info(f"Found {len(all_messages)} messages to process")
        
        if test_mode:
            logging.info("Test mode active - no actual deletion")
            return
        
        for message in all_messages:
            try:
                gmail_service.users().messages().delete(userId='me', id=message['id']).execute()
                logging.info(f"Removed message: {message['id']}")
            except Exception as error:
                logging.error(f"Failed to remove message {message['id']}: {error}")
    
    except Exception as error:
        logging.error(f"Cleanup process failed: {error}")

def run_cleanup():
    creds = authenticate_gmail()
    check_permissions(creds)
    service = build('gmail', 'v1', credentials=creds)
    cleanup_old_messages(service)

if __name__ == '__main__':
    run_cleanup()

I keep getting 403 errors saying insufficient permissions even though I’m using the modify scope. Do I need to publish this app or is there another way to get deletion permissions for personal use?

Had the exact same 403 error when I built my cleanup script. Your scope’s fine - gmail.modify works for deletion. The real issue is your OAuth consent screen setup in Google Cloud Console. Add your email as a test user in the OAuth consent screen settings. Yeah, even for personal projects. Also double-check that Gmail API is enabled in your project’s API library. Sometimes you need to completely regenerate credentials after these changes. Delete your auth_token.pickle file and re-run auth after adding yourself as a test user. Fresh token generation fixed my 403 errors. Your script looks good otherwise - I used almost the same approach for my Gmail cleanup.

Check your client_secrets.json file first. I had similar permission issues and found my OAuth app type was wrong in Google Cloud Console. Make sure you created it as Desktop application, not Web application - the redirect URI handling’s different and can break authentication. Also verify your Google Cloud project has billing enabled. Sometimes APIs get restricted without billing even for personal use. Watch your date format in the search query too. Gmail’s search is picky - stick with ‘YYYY/MM/DD’ format. When I debugged my cleanup script, I added small delays between deletion requests to avoid rate limits that throw permission-like errors.

Double-check you’re using the right Google account when authorizing. I made this exact mistake - had my work Gmail as primary but was trying to clean my personal account. Also try adding https://www.googleapis.com/auth/gmail.readonly scope temporarily to test if basic access works first. If readonly works but modify doesn’t, there’s definitely something wrong with your consent screen config.