Skip to main content
RapidDev - Software Development Agency
API AutomationsGmailOAuth 2.0

How to Automate Gmail Inbox Sorting using the API

Automate Gmail inbox sorting using the Gmail API's users.settings.filters.create endpoint to apply labels to future emails, and users.messages.batchModify to re-sort existing messages. Key auth requires OAuth 2.0 with gmail.settings.basic + gmail.modify scopes (both Restricted, requiring OAuth verification). Watch the per-user quota: batchModify costs 50 units per call against a 15,000 units/minute limit.

Need help automating? Talk to an expert
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate7 min read30-60 minutesGmailMay 2026RapidDev Engineering Team
TL;DR

Automate Gmail inbox sorting using the Gmail API's users.settings.filters.create endpoint to apply labels to future emails, and users.messages.batchModify to re-sort existing messages. Key auth requires OAuth 2.0 with gmail.settings.basic + gmail.modify scopes (both Restricted, requiring OAuth verification). Watch the per-user quota: batchModify costs 50 units per call against a 15,000 units/minute limit.

API Quick Reference

Auth

OAuth 2.0

Rate limit

15,000 quota units/minute per user

Format

JSON

SDK

Available

Understanding the Gmail API

The Gmail API is a RESTful API that gives you full programmatic access to Gmail mailboxes. It operates on a resource model where users, messages, threads, labels, drafts, and filters are all first-class resources, each with their own endpoints under the base URL https://gmail.googleapis.com/gmail/v1/users/{userId}.

For inbox sorting automation, you'll interact with two distinct subsystems: the filters API (users.settings.filters) for creating persistent rules on future mail, and the messages API (users.messages) for bulk-modifying existing messages. These are completely separate code paths — filters do not retroactively apply to messages already in your inbox.

The API uses OAuth 2.0 for all authentication. The gmail.modify scope — required for label application — is classified as Restricted by Google, meaning your app must complete OAuth verification and a CASA (Cloud Application Security Assessment) if you store message content server-side. For internal Workspace automation using Service Account + Domain-Wide Delegation, verification requirements differ. Official docs: https://developers.google.com/workspace/gmail/api

Base URLhttps://gmail.googleapis.com/gmail/v1

Setting Up Gmail API Authentication

Gmail uses OAuth 2.0 (3-legged flow) for user-delegated access, meaning each user must authorize your application. Access tokens expire after 3,600 seconds (1 hour), but refresh tokens let you obtain new ones automatically. Note: in Testing mode on the consent screen, refresh tokens expire after 7 days — you must publish your app to get long-lived tokens.

  1. 1Go to https://console.cloud.google.com and create a new project or select an existing one
  2. 2Navigate to APIs & Services → Library, search for 'Gmail API', and click Enable
  3. 3Go to APIs & Services → OAuth consent screen. Set User Type to 'External' (or 'Internal' for Workspace-only apps). Fill in app name, support email, and developer contact
  4. 4Under Scopes, add: https://www.googleapis.com/auth/gmail.settings.basic and https://www.googleapis.com/auth/gmail.modify
  5. 5Go to APIs & Services → Credentials → Create Credentials → OAuth Client ID. Choose 'Desktop app' for scripts or 'Web application' for server apps
  6. 6Download the credentials JSON file and save as credentials.json in your project directory
  7. 7Run your script once interactively to complete the OAuth consent flow — this saves a token.json file with refresh token
  8. 8For server automation, use Service Account + Domain-Wide Delegation: create a Service Account, download its JSON key, enable DWD in Google Workspace Admin Console → Security → API Controls → Manage Domain Wide Delegation
auth.py
1import os
2import json
3from google.oauth2.credentials import Credentials
4from google_auth_oauthlib.flow import InstalledAppFlow
5from google.auth.transport.requests import Request
6
7SCOPES = [
8 'https://www.googleapis.com/auth/gmail.settings.basic',
9 'https://www.googleapis.com/auth/gmail.modify',
10 'https://www.googleapis.com/auth/gmail.labels'
11]
12
13def get_credentials():
14 creds = None
15 if os.path.exists('token.json'):
16 creds = Credentials.from_authorized_user_file('token.json', SCOPES)
17 if not creds or not creds.valid:
18 if creds and creds.expired and creds.refresh_token:
19 creds.refresh(Request())
20 else:
21 flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
22 creds = flow.run_local_server(port=0)
23 with open('token.json', 'w') as token:
24 token.write(creds.to_json())
25 return creds

Security notes

  • Store credentials.json and token.json outside your repository — add them to .gitignore immediately
  • Never hardcode OAuth client secrets in source code — use environment variables or secret managers
  • The gmail.modify scope is Restricted — your app requires OAuth verification before publishing to external users
  • Refresh tokens in Testing mode expire after 7 days — publish your consent screen for production use
  • For server-to-server automation, use Service Account + DWD instead of storing user refresh tokens

Key endpoints

POST/gmail/v1/users/{userId}/settings/filters

Creates a Gmail filter that automatically applies actions (add/remove labels, archive, forward) to future incoming messages matching specified criteria. Filters only apply to email received after creation.

ParameterTypeRequiredDescription
userIdstringrequiredThe user's email address or 'me' for the authenticated user
criteria.fromstringoptionalSender email address pattern to match
criteria.subjectstringoptionalSubject line text to match
criteria.querystringoptionalFull Gmail search syntax query (e.g., 'has:attachment is:unread')
action.addLabelIdsarrayoptionalArray of label IDs to apply to matched messages
action.removeLabelIdsarrayoptionalArray of label IDs to remove (use INBOX to archive)

Request

json
1{"criteria":{"from":"newsletter@company.com","subject":"Weekly Update"},"action":{"addLabelIds":["Label_123"],"removeLabelIds":["INBOX"]}}

Response

json
1{"id":"ANe1BmjmPmvFJzVf","criteria":{"from":"newsletter@company.com","subject":"Weekly Update"},"action":{"addLabelIds":["Label_123"],"removeLabelIds":["INBOX"]}}
POST/gmail/v1/users/{userId}/labels

Creates a new custom label in the user's mailbox. Labels are used as categories/folders and must be created before being referenced in filters or batchModify calls.

ParameterTypeRequiredDescription
namestringrequiredDisplay name for the label (must be unique in the mailbox)
messageListVisibilitystringoptionalWhether messages with this label appear in message list: 'show' or 'hide'
labelListVisibilitystringoptionalVisibility in label list: 'labelShow', 'labelShowIfUnread', or 'labelHide'
color.textColorstringoptionalHex color for label text
color.backgroundColorstringoptionalHex color for label background

Request

json
1{"name":"Newsletters","messageListVisibility":"show","labelListVisibility":"labelShow","color":{"textColor":"#ffffff","backgroundColor":"#16a765"}}

Response

json
1{"id":"Label_789","name":"Newsletters","messageListVisibility":"show","labelListVisibility":"labelShow","type":"user"}
GET/gmail/v1/users/{userId}/messages

Lists message IDs matching a search query. Returns only {id, threadId} pairs — you need a separate messages.get call to fetch actual content. Use this to find existing messages to retroactively sort.

ParameterTypeRequiredDescription
qstringoptionalGmail search syntax query (e.g., 'from:newsletter@company.com is:inbox')
maxResultsnumberoptionalMaximum messages to return, 1-500
pageTokenstringoptionalToken from previous response for pagination
labelIdsarrayoptionalFilter by label IDs (e.g., ['INBOX'])
includeSpamTrashbooleanoptionalWhether to include Spam and Trash in results

Response

json
1{"messages":[{"id":"18c3a2b1d4e5f6a7","threadId":"18c3a2b1d4e5f6a7"},{"id":"18c3a2b1d4e5f699","threadId":"18c3a2b1d4e5f699"}],"nextPageToken":"06614753730951641","resultSizeEstimate":142}
POST/gmail/v1/users/{userId}/messages/batchModify

Applies label changes to up to 1,000 messages in a single API call. Costs 50 quota units per call regardless of message count. Use this to retroactively sort existing messages into labels.

ParameterTypeRequiredDescription
idsarrayrequiredArray of message IDs to modify, maximum 1,000 per request
addLabelIdsarrayoptionalLabel IDs to add to all specified messages
removeLabelIdsarrayoptionalLabel IDs to remove from all specified messages

Request

json
1{"ids":["18c3a2b1d4e5f6a7","18c3a2b1d4e5f699"],"addLabelIds":["Label_789"],"removeLabelIds":["INBOX"]}

Response

json
1{}

Step-by-step automation

1

Authenticate and Build Gmail Service Client

Why: All Gmail API calls require a valid OAuth 2.0 access token — without it every request returns 401.

Run the OAuth flow to get credentials and build the Gmail API service client. On first run this opens a browser window for user consent. Subsequent runs use the saved refresh token automatically. The userId 'me' refers to the authenticated user throughout all subsequent calls.

request.sh
1# First get an access token via OAuth (use gcloud for quick testing)
2ACCESS_TOKEN=$(gcloud auth print-access-token)
3
4# Verify authentication by listing labels
5curl -H "Authorization: Bearer $ACCESS_TOKEN" \
6 'https://gmail.googleapis.com/gmail/v1/users/me/labels'

Pro tip: Use userId='me' everywhere — it automatically resolves to the authenticated user's address. Hardcoding email addresses in userId breaks Service Account impersonation flows.

Expected result: A Gmail service object is returned. You can verify by calling service.users().labels().list(userId='me').execute() and seeing your existing labels.

2

Create Custom Labels for Sorting Categories

Why: Labels must exist before they can be assigned to messages — you can't reference a label ID that doesn't exist.

Create the label categories you want to sort email into. Each label needs a unique name within the mailbox. The API supports custom colors using hex codes — check Google's documentation for the list of supported color values (not all hex values are valid). The response includes the label's ID which you'll need for subsequent filter and batchModify calls.

request.sh
1curl -X POST \
2 -H "Authorization: Bearer $ACCESS_TOKEN" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "name": "Newsletters",
6 "messageListVisibility": "show",
7 "labelListVisibility": "labelShow",
8 "color": {"textColor": "#ffffff", "backgroundColor": "#16a765"}
9 }' \
10 'https://gmail.googleapis.com/gmail/v1/users/me/labels'

Pro tip: Gmail has a limit of 10,000 labels per mailbox. If you're building a multi-tenant system, be conservative — create one label per category, not per user.

Expected result: Returns the new label object including its ID (e.g., Label_123abc). The label appears immediately in Gmail's sidebar.

3

Create API Filters for Future Email Sorting

Why: Filters are persistent rules that Gmail applies automatically to all future incoming email — this is the 'set it and forget it' part of inbox automation.

Create filters using users.settings.filters.create. Each filter has criteria (what to match) and an action (what to do). The criteria object accepts from, to, subject, query (full Gmail search syntax), hasAttachment, excludeChats, and size/sizeComparison. The action object accepts addLabelIds, removeLabelIds, and forward. A filter with removeLabelIds: ['INBOX'] effectively archives matching messages.

request.sh
1curl -X POST \
2 -H "Authorization: Bearer $ACCESS_TOKEN" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "criteria": {
6 "from": "newsletters@substack.com",
7 "query": "is:unread"
8 },
9 "action": {
10 "addLabelIds": ["Label_789"],
11 "removeLabelIds": ["INBOX"]
12 }
13 }' \
14 'https://gmail.googleapis.com/gmail/v1/users/me/settings/filters'

Pro tip: Use Gmail's search syntax in the query field for powerful matching: 'list:' matches all mailing list emails, 'has:attachment larger:5M' matches large attachments, 'from:(site1.com OR site2.com)' matches multiple senders.

Expected result: Returns the filter object with an ID. The filter appears in Gmail Settings → Filters and Blocked Addresses. All future matching emails will be sorted automatically.

4

Retroactively Sort Existing Messages with batchModify

Why: Filters only affect future mail — to clean up your existing inbox you must separately find and relabel messages already there.

Use messages.list with a search query to get matching message IDs, then call messages.batchModify with up to 1,000 IDs at a time. The batchModify endpoint returns an empty 204 response on success. Be careful with quota: each batchModify call costs 50 units, and messages.list costs 5 units per page — scanning 5,000 messages costs at minimum 55 units just for listing.

request.sh
1# Step 1: List matching message IDs
2curl -H "Authorization: Bearer $ACCESS_TOKEN" \
3 'https://gmail.googleapis.com/gmail/v1/users/me/messages?q=from:substack.com+in:inbox&maxResults=500'
4
5# Step 2: Apply label to those messages (replace IDs)
6curl -X POST \
7 -H "Authorization: Bearer $ACCESS_TOKEN" \
8 -H "Content-Type: application/json" \
9 -d '{
10 "ids": ["18c3a2b1d4e5f6a7", "18c3a2b1d4e5f699"],
11 "addLabelIds": ["Label_789"],
12 "removeLabelIds": ["INBOX"]
13 }' \
14 'https://gmail.googleapis.com/gmail/v1/users/me/messages/batchModify'

Pro tip: For large mailboxes with 10,000+ messages to sort, spread the work across multiple runs to avoid hitting the 15,000 units/minute per-user limit. At 50 units per batchModify call with 1,000 messages each, you can process about 300,000 messages per minute before hitting the limit.

Expected result: All matching existing messages receive the new label and are removed from INBOX. The function returns the total count of sorted messages.

Complete working code

This script creates sorting labels, sets up filters for future email, then retroactively sorts all existing matching messages. It handles the three most common sorting categories: newsletters, notifications, and receipts. Run it once to set up your inbox, then the filters keep things organized automatically.

automate_inbox_sorting.py
1#!/usr/bin/env python3
2"""Gmail Inbox Sorter — creates labels, filters, and retroactively sorts existing mail."""
3import os
4import time
5import logging
6from googleapiclient.discovery import build
7from google.oauth2.credentials import Credentials
8from google_auth_oauthlib.flow import InstalledAppFlow
9from google.auth.transport.requests import Request
10
11logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s')
12log = logging.getLogger(__name__)
13
14SCOPES = [
15 'https://www.googleapis.com/auth/gmail.settings.basic',
16 'https://www.googleapis.com/auth/gmail.modify',
17 'https://www.googleapis.com/auth/gmail.labels'
18]
19
20SORTING_RULES = [
21 {
22 'label_name': 'Newsletters',
23 'label_color': {'textColor': '#ffffff', 'backgroundColor': '#16a765'},
24 'filter_criteria': {'query': 'list: OR unsubscribe'},
25 'sort_query': 'list: OR unsubscribe in:inbox'
26 },
27 {
28 'label_name': 'Notifications',
29 'label_color': {'textColor': '#ffffff', 'backgroundColor': '#4986e7'},
30 'filter_criteria': {'from': 'noreply@ OR no-reply@'},
31 'sort_query': 'from:(noreply OR no-reply) in:inbox'
32 },
33 {
34 'label_name': 'Receipts',
35 'label_color': {'textColor': '#000000', 'backgroundColor': '#f691b2'},
36 'filter_criteria': {'query': 'subject:(receipt OR order OR invoice)'},
37 'sort_query': 'subject:(receipt OR order OR invoice) in:inbox'
38 }
39]
40
41def get_credentials():
42 creds = None
43 if os.path.exists('token.json'):
44 creds = Credentials.from_authorized_user_file('token.json', SCOPES)
45 if not creds or not creds.valid:
46 if creds and creds.expired and creds.refresh_token:
47 creds.refresh(Request())
48 else:
49 flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
50 creds = flow.run_local_server(port=0)
51 with open('token.json', 'w') as f:
52 f.write(creds.to_json())
53 return creds
54
55def get_or_create_label(service, name, color):
56 labels = service.users().labels().list(userId='me').execute().get('labels', [])
57 for label in labels:
58 if label['name'] == name:
59 log.info(f'Label exists: {name} ({label["id"]})')
60 return label['id']
61 result = service.users().labels().create(
62 userId='me', body={'name': name, 'messageListVisibility': 'show',
63 'labelListVisibility': 'labelShow', 'color': color}
64 ).execute()
65 log.info(f'Created label: {name} ({result["id"]})')
66 return result['id']
67
68def sort_existing_mail(service, query, label_id):
69 ids, page_token = [], None
70 while True:
71 params = {'userId': 'me', 'q': query, 'maxResults': 500}
72 if page_token:
73 params['pageToken'] = page_token
74 res = service.users().messages().list(**params).execute()
75 ids.extend([m['id'] for m in res.get('messages', [])])
76 page_token = res.get('nextPageToken')
77 if not page_token:
78 break
79 for i in range(0, len(ids), 1000):
80 service.users().messages().batchModify(
81 userId='me',
82 body={'ids': ids[i:i+1000], 'addLabelIds': [label_id], 'removeLabelIds': ['INBOX']}
83 ).execute()
84 time.sleep(0.5)
85 return len(ids)
86
87def main():
88 service = build('gmail', 'v1', credentials=get_credentials())
89 for rule in SORTING_RULES:
90 label_id = get_or_create_label(service, rule['label_name'], rule['label_color'])
91 service.users().settings().filters().create(
92 userId='me',
93 body={'criteria': rule['filter_criteria'],
94 'action': {'addLabelIds': [label_id], 'removeLabelIds': ['INBOX']}}
95 ).execute()
96 log.info(f'Filter created for {rule["label_name"]}')
97 count = sort_existing_mail(service, rule['sort_query'], label_id)
98 log.info(f'Sorted {count} existing messages into {rule["label_name"]}')
99
100if __name__ == '__main__':
101 main()

Error handling

403Request had insufficient authentication scopes.
Cause

Your OAuth token was issued with scopes that don't include gmail.modify or gmail.settings.basic. The token is valid but lacks permissions.

Fix

Delete token.json and re-run the OAuth flow with the correct SCOPES list. Ensure gmail.modify and gmail.settings.basic are both in your consent screen's scope list.

Retry strategy

No retry — user must re-authorize with correct scopes.

403User Rate Limit Exceeded
Cause

Your application exceeded the 15,000 quota units per minute for this user. batchModify at 50 units/call is the most likely culprit when processing large backlogs.

Fix

Implement exponential backoff. Add sleep(0.5) between batch calls. Spread large sorting jobs over multiple runs.

Retry strategy

Exponential backoff: wait 2^n seconds (1s, 2s, 4s, 8s, 16s, 32s, max 64s) then retry.

400Invalid label ID
Cause

You referenced a label ID that doesn't exist in the user's mailbox, or used a system label name (like 'INBOX') in the addLabelIds field where it's not allowed.

Fix

Verify label IDs exist by calling users.labels.list first. System label IDs like INBOX, SENT, STARRED must be used exactly as uppercase strings.

Retry strategy

No retry — fix the label ID and resubmit.

429Too Many Requests
Cause

Hit the per-project quota limit of 1,200,000 units/minute or the sending limit. For batchModify operations, this usually means too many concurrent users hitting the API.

Fix

Implement exponential backoff. For multi-user apps, add per-user concurrency limits. Consider spreading operations with time.sleep() between batches.

Retry strategy

Exponential backoff starting at 1s, doubling each attempt, max 64s with jitter.

401Invalid Credentials
Cause

Access token has expired (TTL is 3,600 seconds). In Testing mode, refresh tokens expire after 7 days.

Fix

The Google client libraries handle token refresh automatically if you use the Credentials class correctly. Ensure your code checks creds.valid and calls creds.refresh(Request()) when expired.

Retry strategy

Refresh the token immediately, then retry the request once.

Rate Limits for Gmail API

ScopeLimitWindow
Per user15,000 quota unitsper minute
Per project1,200,000 quota unitsper minute
Filter limit1,000 filtersper mailbox (hard limit, not rate)
Label limit10,000 labelsper mailbox (hard limit)
retry-handler.ts
1import time
2import random
3from googleapiclient.errors import HttpError
4
5def execute_with_backoff(request, max_retries=6):
6 for attempt in range(max_retries):
7 try:
8 return request.execute()
9 except HttpError as e:
10 if e.resp.status in [403, 429, 500, 503]:
11 wait = min((2 ** attempt) + random.uniform(0, 1), 64)
12 print(f'Rate limited, waiting {wait:.1f}s (attempt {attempt+1})')
13 time.sleep(wait)
14 else:
15 raise
16 raise Exception('Max retries exceeded')
  • Use batchModify instead of individual modify calls — 1 call for 1,000 messages vs 1,000 separate calls
  • Cache label IDs after the first labels.list call — avoid re-fetching on every run
  • Add 500ms sleep between batch calls when processing large existing backlogs
  • Schedule inbox-sorting jobs during off-peak hours if processing thousands of messages
  • Monitor quota usage in Google Cloud Console → APIs & Services → Gmail API → Quotas

Security checklist

  • Store OAuth credentials (credentials.json, token.json) outside your repository — add to .gitignore
  • Request only the minimum required scopes: use gmail.labels for label-only operations, only add gmail.modify when you need to change message labels
  • The gmail.modify scope is Restricted — complete OAuth verification before deploying to external users
  • For Workspace automation, use Service Account + Domain-Wide Delegation instead of storing user refresh tokens
  • Rotate Service Account keys every 90 days via Google Cloud Console
  • Audit filter creation logs — a compromised app could create filters to forward emails to external addresses
  • Use HTTPS for all API calls (the Google client libraries enforce this by default)
  • Implement token refresh error handling to gracefully re-authenticate rather than exposing errors to end users

Automation use cases

Newsletter Zero

beginner

Automatically sort all newsletters and mailing lists out of inbox into a dedicated label, keeping the inbox only for direct human communication.

Sales Pipeline Sorter

intermediate

Route emails from specific lead sources or domains into CRM-linked labels, then use labels as triggers for follow-up automation.

Multi-Account Inbox Manager

advanced

Use Service Account + DWD to sort inboxes across all users in a Google Workspace organization simultaneously with standardized label taxonomies.

Receipt Archiver

intermediate

Identify order confirmations and receipts by subject line patterns, label them, and optionally extract data for expense tracking.

No-code alternatives

Don't want to write code? These platforms can automate the same workflows visually.

Zapier

Free tier available (100 tasks/month); Starter from $19.99/month

Zapier's Gmail integration supports triggers on new emails and actions to add labels, making it easy to build sorting rules without code.

Pros
  • + No code required
  • + GUI-based rule creation
  • + 500+ integration triggers for routing
Cons
  • - Limited to 100 tasks/month on free tier
  • - Can't batch-process existing messages
  • - No access to Gmail's native filter system

Make (Integromat)

Free tier (1,000 ops/month); Core from $9/month

Make offers a visual scenario builder that watches for new Gmail messages and applies labels based on conditions.

Pros
  • + Visual flow builder
  • + More complex logic than Zapier
  • + Better value on paid tiers
Cons
  • - Learning curve for complex scenarios
  • - Free tier limited to 1,000 operations/month
  • - No retroactive sorting

n8n

Free self-hosted; Cloud from €20/month

Self-hosted n8n provides Gmail nodes for reading and modifying messages, with full control over sorting logic in a visual workflow editor.

Pros
  • + Self-hostable (free)
  • + Full Gmail API access
  • + Can run on a schedule for retroactive sorting
Cons
  • - Requires hosting setup
  • - More technical than Zapier/Make
  • - No managed infrastructure

Best practices

  • Always create filters before running retroactive sorting — otherwise the filter creation quota cost (5 units) is wasted on messages already sorted
  • Use Gmail's native search syntax in filter criteria: 'list:' reliably matches mailing lists, 'has:attachment larger:5M' catches large files
  • Test filter criteria in Gmail's search bar before creating filters via API — what you see is what the API will match
  • Handle the case where a label already exists: call labels.list and reuse existing IDs rather than failing on duplicate creation
  • Use batchModify's 1,000-message limit aggressively — it's 50 quota units per call regardless of how many messages are in the batch
  • For Workspace deployments, prefer Service Account + DWD over per-user OAuth to avoid managing thousands of refresh tokens
  • Log all filter IDs created by your automation so you can programmatically clean them up later

Ask AI to help

Copy one of these prompts to get a personalized, working implementation.

ChatGPT / Claude Prompt

I'm using the Gmail API (Python, google-api-python-client) to automate inbox sorting. I have a service object and a list of SORTING_RULES with label_name, filter_criteria, and sort_query fields. I'm getting a 403 insufficientPermissions error when calling users().settings().filters().create(). My OAuth scopes are gmail.labels and gmail.modify. What scope am I missing and why does gmail.modify not cover filter creation?

Lovable / V0 Prompt

Build a Gmail inbox sorting dashboard using the Gmail API. The app should: 1) Show all existing labels with message counts, 2) Display active filters with their criteria and actions, 3) Allow creating new sorting rules via a form (from, subject, query fields + label selection), 4) Show a log of recently sorted messages. Use Supabase for storing rule configurations. Connect to Gmail API via a Supabase Edge Function that handles OAuth token refresh.

Frequently asked questions

Do Gmail API filters apply to existing messages in my inbox?

No. users.settings.filters.create only applies to future incoming email. To sort existing messages, you must separately use users.messages.list with a search query to get message IDs, then call users.messages.batchModify to apply labels. The complete automation script above handles both steps.

Is the Gmail API free?

The Gmail API itself is free with no per-call charges. However, using Restricted scopes (gmail.modify, gmail.readonly) requires completing Google's OAuth verification process and potentially a CASA security assessment, which can have associated costs for the audit. Compute costs for running your automation server are separate.

What happens when I hit the rate limit?

You'll receive a 403 userRateLimitExceeded or 429 Too Many Requests error. The per-user limit is 15,000 quota units per minute. batchModify costs 50 units per call, so you can run about 300 batch calls per minute. Implement exponential backoff: wait 2^n seconds (starting at 1s, max 64s) and retry.

Can I use a Service Account instead of OAuth for Gmail?

Yes, for Google Workspace (G Suite) organizations only. You need to enable Domain-Wide Delegation: create a Service Account in Google Cloud Console, enable DWD in Workspace Admin Console → Security → API Controls, and use setSubject() or with_subject() to impersonate a user. Personal Gmail accounts cannot use Service Accounts — they require 3-legged OAuth.

Why does my OAuth token expire after 7 days?

When your OAuth consent screen is in 'Testing' mode, Google issues refresh tokens that expire after 7 days. To get long-lived tokens, publish your OAuth consent screen (you don't need to submit for verification to go from Testing to Published for Internal Workspace apps). External apps need verification before publishing.

What's the difference between filter criteria 'from' vs 'query' fields?

The 'from' field is a simple sender match. The 'query' field accepts full Gmail search syntax including operators like 'list:', 'has:attachment', 'larger:5M', 'subject:', 'to:', and boolean operators. For complex matching, always use 'query' — it's the same search language you type in Gmail's search bar.

Can RapidDev help build a custom Gmail integration?

Yes. RapidDev has built 600+ apps including email automation systems. Whether you need inbox sorting, lead capture pipelines, or full support triage with CRM integration, we can scope and build it. Contact us for a free consultation at rapidevelopers.com.

RapidDev

Need this automated?

Our team has built 600+ apps with API automations. We can build this for you.

Book a free consultation

Skip the coding — we'll build it for you

Our experts have built 600+ API automations. From prototype to production in days, not weeks.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.