[{"data":1,"prerenderedAt":2130},["ShallowReactive",2],{"page-\u002Fautomating-side-hustle-operations-with-apis\u002Fconnecting-crm-email-apis\u002Fautomate-gmail-with-python-and-gmail-api\u002F":3,"faq-schema-\u002Fautomating-side-hustle-operations-with-apis\u002Fconnecting-crm-email-apis\u002Fautomate-gmail-with-python-and-gmail-api\u002F":2111},{"id":4,"title":5,"body":6,"description":16,"extension":2105,"meta":2106,"navigation":214,"path":2107,"seo":2108,"stem":2109,"__hash__":2110},"content\u002Fautomating-side-hustle-operations-with-apis\u002Fconnecting-crm-email-apis\u002Fautomate-gmail-with-python-and-gmail-api\u002Findex.md","Automate Gmail with Python and Gmail API: Production-Ready Implementation Guide",{"type":7,"value":8,"toc":2096},"minimark",[9,13,17,39,44,47,119,123,126,639,644,665,669,672,1266,1270,1309,1313,1320,1841,1845,1875,1879,1882,1944,1948,2008,2012,2027,2033,2067,2092],[10,11,5],"h1",{"id":12},"automate-gmail-with-python-and-gmail-api-production-ready-implementation-guide",[14,15,16],"p",{},"Direct, implementation-focused guide to programmatically control Gmail using Python and the official Google API. This tutorial covers secure OAuth2 setup, raw MIME decoding, quota-aware pagination, and production error handling for scalable side-hustle workflows.",[18,19,20,24,27,30],"ul",{},[21,22,23],"li",{},"Replace legacy IMAP\u002FSMTP with official API endpoints for higher reliability and structured JSON responses",[21,25,26],{},"Implement token refresh logic to maintain unattended automation",[21,28,29],{},"Handle 429 quota errors and batch requests to maximize throughput",[21,31,32,33,38],{},"Integrate email parsing into broader ",[34,35,37],"a",{"href":36},"\u002Fautomating-side-hustle-operations-with-apis\u002Fconnecting-crm-email-apis\u002F","Connecting CRM & Email APIs"," pipelines for lead routing and customer sync",[40,41,43],"h2",{"id":42},"gcp-console-setup-oauth2-configuration","GCP Console Setup & OAuth2 Configuration",[14,45,46],{},"Establish secure API access and generate client credentials without exposing sensitive keys. Google's infrastructure requires explicit consent and scoped permissions before any programmatic interaction.",[48,49,50,63,78,89,104],"ol",{},[21,51,52,53,57,58,62],{},"Navigate to the Google Cloud Console and create a new project. Enable the ",[54,55,56],"strong",{},"Gmail API"," under ",[59,60,61],"code",{},"APIs & Services > Library",".",[21,64,65,66,69,70,73,74,77],{},"Configure the ",[54,67,68],{},"OAuth Consent Screen",". Select ",[59,71,72],{},"External"," for public-facing side-hustle tools, or ",[59,75,76],{},"Internal"," for private business automation. Add your developer email for testing.",[21,79,80,81,84,85,88],{},"Create OAuth 2.0 Client IDs (",[59,82,83],{},"Desktop App"," type). Download the resulting ",[59,86,87],{},"credentials.json"," file.",[21,90,91,92,95,96,99,100,103],{},"Restrict scopes strictly to ",[59,93,94],{},"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fgmail.modify",". Avoid ",[59,97,98],{},"gmail.readonly"," if you plan to send or label messages, and never request ",[59,101,102],{},"gmail.modify"," unless necessary to minimize user consent friction.",[21,105,106,107,109,110,113,114,118],{},"Store all credentials and tokens in environment variables or a secure vault. Never commit ",[59,108,87],{}," or ",[59,111,112],{},"token.json"," to version control. Align this credential architecture with broader ",[34,115,117],{"href":116},"\u002Fautomating-side-hustle-operations-with-apis\u002F","Automating Side-Hustle Operations with APIs"," deployment patterns to ensure consistent secret rotation and audit trails.",[40,120,122],{"id":121},"authentication-flow-token-management","Authentication Flow & Token Management",[14,124,125],{},"Implement the OAuth2 authorization code flow with automatic token refresh for headless scripts. The initial interactive step is required once; subsequent runs should operate silently using a stored refresh token.",[127,128,133],"pre",{"className":129,"code":130,"language":131,"meta":132,"style":132},"language-python shiki shiki-themes github-light github-dark","import os\nimport logging\nfrom google.oauth2.credentials import Credentials\nfrom google_auth_oauthlib.flow import InstalledAppFlow\nfrom google.auth.transport.requests import Request\nfrom google.auth.exceptions import RefreshError\n\nlogging.basicConfig(level=logging.INFO, format=\"%(asctime)s - %(levelname)s - %(message)s\")\n\nSCOPES = [\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fgmail.modify\"]\nTOKEN_PATH = os.getenv(\"GMAIL_TOKEN_PATH\", \"token.json\")\nCREDENTIALS_PATH = os.getenv(\"GMAIL_CREDENTIALS_PATH\", \"credentials.json\")\n\ndef authenticate_gmail() -> Credentials:\n creds = None\n if os.path.exists(TOKEN_PATH):\n creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)\n\n if not creds or not creds.valid:\n try:\n if creds and creds.expired and creds.refresh_token:\n logging.info(\"Refreshing expired token...\")\n creds.refresh(Request())\n else:\n logging.info(\"No valid token found. Starting interactive OAuth flow...\")\n flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES)\n creds = flow.run_local_server(port=0)\n except RefreshError as e:\n logging.error(f\"Token refresh failed: {e}. Deleting stale token.json and restarting flow.\")\n if os.path.exists(TOKEN_PATH):\n os.remove(TOKEN_PATH)\n return authenticate_gmail()\n\n with open(TOKEN_PATH, \"w\") as token_file:\n token_file.write(creds.to_json())\n \n return creds\n","python","",[59,134,135,148,156,170,183,196,209,216,267,272,290,311,331,336,349,360,374,392,397,415,424,442,453,459,467,477,496,516,531,557,568,578,587,592,619,625,631],{"__ignoreMap":132},[136,137,140,144],"span",{"class":138,"line":139},"line",1,[136,141,143],{"class":142},"szBVR","import",[136,145,147],{"class":146},"sVt8B"," os\n",[136,149,151,153],{"class":138,"line":150},2,[136,152,143],{"class":142},[136,154,155],{"class":146}," logging\n",[136,157,159,162,165,167],{"class":138,"line":158},3,[136,160,161],{"class":142},"from",[136,163,164],{"class":146}," google.oauth2.credentials ",[136,166,143],{"class":142},[136,168,169],{"class":146}," Credentials\n",[136,171,173,175,178,180],{"class":138,"line":172},4,[136,174,161],{"class":142},[136,176,177],{"class":146}," google_auth_oauthlib.flow ",[136,179,143],{"class":142},[136,181,182],{"class":146}," InstalledAppFlow\n",[136,184,186,188,191,193],{"class":138,"line":185},5,[136,187,161],{"class":142},[136,189,190],{"class":146}," google.auth.transport.requests ",[136,192,143],{"class":142},[136,194,195],{"class":146}," Request\n",[136,197,199,201,204,206],{"class":138,"line":198},6,[136,200,161],{"class":142},[136,202,203],{"class":146}," google.auth.exceptions ",[136,205,143],{"class":142},[136,207,208],{"class":146}," RefreshError\n",[136,210,212],{"class":138,"line":211},7,[136,213,215],{"emptyLinePlaceholder":214},true,"\n",[136,217,219,222,226,229,232,236,239,242,244,248,251,254,257,259,262,264],{"class":138,"line":218},8,[136,220,221],{"class":146},"logging.basicConfig(",[136,223,225],{"class":224},"s4XuR","level",[136,227,228],{"class":142},"=",[136,230,231],{"class":146},"logging.",[136,233,235],{"class":234},"sj4cs","INFO",[136,237,238],{"class":146},", ",[136,240,241],{"class":224},"format",[136,243,228],{"class":142},[136,245,247],{"class":246},"sZZnC","\"",[136,249,250],{"class":234},"%(asctime)s",[136,252,253],{"class":246}," - ",[136,255,256],{"class":234},"%(levelname)s",[136,258,253],{"class":246},[136,260,261],{"class":234},"%(message)s",[136,263,247],{"class":246},[136,265,266],{"class":146},")\n",[136,268,270],{"class":138,"line":269},9,[136,271,215],{"emptyLinePlaceholder":214},[136,273,275,278,281,284,287],{"class":138,"line":274},10,[136,276,277],{"class":234},"SCOPES",[136,279,280],{"class":142}," =",[136,282,283],{"class":146}," [",[136,285,286],{"class":246},"\"https:\u002F\u002Fwww.googleapis.com\u002Fauth\u002Fgmail.modify\"",[136,288,289],{"class":146},"]\n",[136,291,293,296,298,301,304,306,309],{"class":138,"line":292},11,[136,294,295],{"class":234},"TOKEN_PATH",[136,297,280],{"class":142},[136,299,300],{"class":146}," os.getenv(",[136,302,303],{"class":246},"\"GMAIL_TOKEN_PATH\"",[136,305,238],{"class":146},[136,307,308],{"class":246},"\"token.json\"",[136,310,266],{"class":146},[136,312,314,317,319,321,324,326,329],{"class":138,"line":313},12,[136,315,316],{"class":234},"CREDENTIALS_PATH",[136,318,280],{"class":142},[136,320,300],{"class":146},[136,322,323],{"class":246},"\"GMAIL_CREDENTIALS_PATH\"",[136,325,238],{"class":146},[136,327,328],{"class":246},"\"credentials.json\"",[136,330,266],{"class":146},[136,332,334],{"class":138,"line":333},13,[136,335,215],{"emptyLinePlaceholder":214},[136,337,339,342,346],{"class":138,"line":338},14,[136,340,341],{"class":142},"def",[136,343,345],{"class":344},"sScJk"," authenticate_gmail",[136,347,348],{"class":146},"() -> Credentials:\n",[136,350,352,355,357],{"class":138,"line":351},15,[136,353,354],{"class":146}," creds ",[136,356,228],{"class":142},[136,358,359],{"class":234}," None\n",[136,361,363,366,369,371],{"class":138,"line":362},16,[136,364,365],{"class":142}," if",[136,367,368],{"class":146}," os.path.exists(",[136,370,295],{"class":234},[136,372,373],{"class":146},"):\n",[136,375,377,379,381,384,386,388,390],{"class":138,"line":376},17,[136,378,354],{"class":146},[136,380,228],{"class":142},[136,382,383],{"class":146}," Credentials.from_authorized_user_file(",[136,385,295],{"class":234},[136,387,238],{"class":146},[136,389,277],{"class":234},[136,391,266],{"class":146},[136,393,395],{"class":138,"line":394},18,[136,396,215],{"emptyLinePlaceholder":214},[136,398,400,402,405,407,410,412],{"class":138,"line":399},19,[136,401,365],{"class":142},[136,403,404],{"class":142}," not",[136,406,354],{"class":146},[136,408,409],{"class":142},"or",[136,411,404],{"class":142},[136,413,414],{"class":146}," creds.valid:\n",[136,416,418,421],{"class":138,"line":417},20,[136,419,420],{"class":142}," try",[136,422,423],{"class":146},":\n",[136,425,427,429,431,434,437,439],{"class":138,"line":426},21,[136,428,365],{"class":142},[136,430,354],{"class":146},[136,432,433],{"class":142},"and",[136,435,436],{"class":146}," creds.expired ",[136,438,433],{"class":142},[136,440,441],{"class":146}," creds.refresh_token:\n",[136,443,445,448,451],{"class":138,"line":444},22,[136,446,447],{"class":146}," logging.info(",[136,449,450],{"class":246},"\"Refreshing expired token...\"",[136,452,266],{"class":146},[136,454,456],{"class":138,"line":455},23,[136,457,458],{"class":146}," creds.refresh(Request())\n",[136,460,462,465],{"class":138,"line":461},24,[136,463,464],{"class":142}," else",[136,466,423],{"class":146},[136,468,470,472,475],{"class":138,"line":469},25,[136,471,447],{"class":146},[136,473,474],{"class":246},"\"No valid token found. Starting interactive OAuth flow...\"",[136,476,266],{"class":146},[136,478,480,483,485,488,490,492,494],{"class":138,"line":479},26,[136,481,482],{"class":146}," flow ",[136,484,228],{"class":142},[136,486,487],{"class":146}," InstalledAppFlow.from_client_secrets_file(",[136,489,316],{"class":234},[136,491,238],{"class":146},[136,493,277],{"class":234},[136,495,266],{"class":146},[136,497,499,501,503,506,509,511,514],{"class":138,"line":498},27,[136,500,354],{"class":146},[136,502,228],{"class":142},[136,504,505],{"class":146}," flow.run_local_server(",[136,507,508],{"class":224},"port",[136,510,228],{"class":142},[136,512,513],{"class":234},"0",[136,515,266],{"class":146},[136,517,519,522,525,528],{"class":138,"line":518},28,[136,520,521],{"class":142}," except",[136,523,524],{"class":146}," RefreshError ",[136,526,527],{"class":142},"as",[136,529,530],{"class":146}," e:\n",[136,532,534,537,540,543,546,549,552,555],{"class":138,"line":533},29,[136,535,536],{"class":146}," logging.error(",[136,538,539],{"class":142},"f",[136,541,542],{"class":246},"\"Token refresh failed: ",[136,544,545],{"class":234},"{",[136,547,548],{"class":146},"e",[136,550,551],{"class":234},"}",[136,553,554],{"class":246},". Deleting stale token.json and restarting flow.\"",[136,556,266],{"class":146},[136,558,560,562,564,566],{"class":138,"line":559},30,[136,561,365],{"class":142},[136,563,368],{"class":146},[136,565,295],{"class":234},[136,567,373],{"class":146},[136,569,571,574,576],{"class":138,"line":570},31,[136,572,573],{"class":146}," os.remove(",[136,575,295],{"class":234},[136,577,266],{"class":146},[136,579,581,584],{"class":138,"line":580},32,[136,582,583],{"class":142}," return",[136,585,586],{"class":146}," authenticate_gmail()\n",[136,588,590],{"class":138,"line":589},33,[136,591,215],{"emptyLinePlaceholder":214},[136,593,595,598,601,604,606,608,611,614,616],{"class":138,"line":594},34,[136,596,597],{"class":142}," with",[136,599,600],{"class":234}," open",[136,602,603],{"class":146},"(",[136,605,295],{"class":234},[136,607,238],{"class":146},[136,609,610],{"class":246},"\"w\"",[136,612,613],{"class":146},") ",[136,615,527],{"class":142},[136,617,618],{"class":146}," token_file:\n",[136,620,622],{"class":138,"line":621},35,[136,623,624],{"class":146}," token_file.write(creds.to_json())\n",[136,626,628],{"class":138,"line":627},36,[136,629,630],{"class":146}," \n",[136,632,634,636],{"class":138,"line":633},37,[136,635,583],{"class":142},[136,637,638],{"class":146}," creds\n",[14,640,641],{},[54,642,643],{},"Implementation Notes:",[18,645,646,652,658],{},[21,647,648,651],{},[59,649,650],{},"creds.refresh()"," silently handles token expiry using the stored refresh token.",[21,653,654,657],{},[59,655,656],{},"RefreshError"," triggers automatic cleanup and fallback to the interactive browser prompt.",[21,659,660,661,664],{},"Always validate ",[59,662,663],{},"creds.valid"," before initializing the API service to prevent mid-execution 401 failures.",[40,666,668],{"id":667},"fetching-filtering-decoding-messages","Fetching, Filtering & Decoding Messages",[14,670,671],{},"Query the inbox efficiently, paginate results safely, and decode base64url-encoded MIME payloads. Raw message retrieval requires careful handling of Google's URL-safe encoding standards.",[127,673,675],{"className":129,"code":674,"language":131,"meta":132,"style":132},"import base64\nimport logging\nfrom email import message_from_bytes\nfrom googleapiclient.discovery import build\nfrom googleapiclient.errors import HttpError\n\ndef fetch_and_parse_messages(service, query=\"is:unread\", max_results=10):\n messages_data = []\n page_token = None\n\n while True:\n try:\n response = service.users().messages().list(\n userId=\"me\",\n q=query,\n maxResults=max_results,\n pageToken=page_token\n ).execute()\n except HttpError as err:\n logging.error(f\"Failed to list messages: {err}\")\n break\n\n messages = response.get(\"messages\", [])\n if not messages:\n break\n\n for msg in messages:\n try:\n raw_msg = service.users().messages().get(\n userId=\"me\", id=msg[\"id\"], format=\"raw\"\n ).execute()\n \n # Gmail uses base64url encoding; standard b64decode will fail on padding\n msg_bytes = base64.urlsafe_b64decode(raw_msg[\"raw\"] + \"=\" * (4 - len(raw_msg[\"raw\"]) % 4))\n email_obj = message_from_bytes(msg_bytes)\n \n messages_data.append({\n \"id\": msg[\"id\"],\n \"subject\": email_obj.get(\"Subject\", \"No Subject\"),\n \"from\": email_obj.get(\"From\", \"Unknown\"),\n \"date\": email_obj.get(\"Date\", \"Unknown\"),\n \"snippet\": email_obj.get(\"X-Gmail-Snippet\", \"\")\n })\n except HttpError as err:\n logging.warning(f\"Skipping message {msg['id']} due to fetch error: {err}\")\n continue\n\n page_token = response.get(\"nextPageToken\")\n if not page_token:\n break\n\n return messages_data\n",[59,676,677,684,690,702,714,726,730,755,765,774,778,788,794,804,817,827,837,847,852,864,884,889,893,909,918,922,926,939,945,955,986,990,994,1000,1054,1064,1068,1073,1087,1107,1125,1142,1160,1166,1177,1213,1219,1224,1238,1248,1253,1258],{"__ignoreMap":132},[136,678,679,681],{"class":138,"line":139},[136,680,143],{"class":142},[136,682,683],{"class":146}," base64\n",[136,685,686,688],{"class":138,"line":150},[136,687,143],{"class":142},[136,689,155],{"class":146},[136,691,692,694,697,699],{"class":138,"line":158},[136,693,161],{"class":142},[136,695,696],{"class":146}," email ",[136,698,143],{"class":142},[136,700,701],{"class":146}," message_from_bytes\n",[136,703,704,706,709,711],{"class":138,"line":172},[136,705,161],{"class":142},[136,707,708],{"class":146}," googleapiclient.discovery ",[136,710,143],{"class":142},[136,712,713],{"class":146}," build\n",[136,715,716,718,721,723],{"class":138,"line":185},[136,717,161],{"class":142},[136,719,720],{"class":146}," googleapiclient.errors ",[136,722,143],{"class":142},[136,724,725],{"class":146}," HttpError\n",[136,727,728],{"class":138,"line":198},[136,729,215],{"emptyLinePlaceholder":214},[136,731,732,734,737,740,742,745,748,750,753],{"class":138,"line":211},[136,733,341],{"class":142},[136,735,736],{"class":344}," fetch_and_parse_messages",[136,738,739],{"class":146},"(service, query",[136,741,228],{"class":142},[136,743,744],{"class":246},"\"is:unread\"",[136,746,747],{"class":146},", max_results",[136,749,228],{"class":142},[136,751,752],{"class":234},"10",[136,754,373],{"class":146},[136,756,757,760,762],{"class":138,"line":218},[136,758,759],{"class":146}," messages_data ",[136,761,228],{"class":142},[136,763,764],{"class":146}," []\n",[136,766,767,770,772],{"class":138,"line":269},[136,768,769],{"class":146}," page_token ",[136,771,228],{"class":142},[136,773,359],{"class":234},[136,775,776],{"class":138,"line":274},[136,777,215],{"emptyLinePlaceholder":214},[136,779,780,783,786],{"class":138,"line":292},[136,781,782],{"class":142}," while",[136,784,785],{"class":234}," True",[136,787,423],{"class":146},[136,789,790,792],{"class":138,"line":313},[136,791,420],{"class":142},[136,793,423],{"class":146},[136,795,796,799,801],{"class":138,"line":333},[136,797,798],{"class":146}," response ",[136,800,228],{"class":142},[136,802,803],{"class":146}," service.users().messages().list(\n",[136,805,806,809,811,814],{"class":138,"line":338},[136,807,808],{"class":224}," userId",[136,810,228],{"class":142},[136,812,813],{"class":246},"\"me\"",[136,815,816],{"class":146},",\n",[136,818,819,822,824],{"class":138,"line":351},[136,820,821],{"class":224}," q",[136,823,228],{"class":142},[136,825,826],{"class":146},"query,\n",[136,828,829,832,834],{"class":138,"line":362},[136,830,831],{"class":224}," maxResults",[136,833,228],{"class":142},[136,835,836],{"class":146},"max_results,\n",[136,838,839,842,844],{"class":138,"line":376},[136,840,841],{"class":224}," pageToken",[136,843,228],{"class":142},[136,845,846],{"class":146},"page_token\n",[136,848,849],{"class":138,"line":394},[136,850,851],{"class":146}," ).execute()\n",[136,853,854,856,859,861],{"class":138,"line":399},[136,855,521],{"class":142},[136,857,858],{"class":146}," HttpError ",[136,860,527],{"class":142},[136,862,863],{"class":146}," err:\n",[136,865,866,868,870,873,875,878,880,882],{"class":138,"line":417},[136,867,536],{"class":146},[136,869,539],{"class":142},[136,871,872],{"class":246},"\"Failed to list messages: ",[136,874,545],{"class":234},[136,876,877],{"class":146},"err",[136,879,551],{"class":234},[136,881,247],{"class":246},[136,883,266],{"class":146},[136,885,886],{"class":138,"line":426},[136,887,888],{"class":142}," break\n",[136,890,891],{"class":138,"line":444},[136,892,215],{"emptyLinePlaceholder":214},[136,894,895,898,900,903,906],{"class":138,"line":455},[136,896,897],{"class":146}," messages ",[136,899,228],{"class":142},[136,901,902],{"class":146}," response.get(",[136,904,905],{"class":246},"\"messages\"",[136,907,908],{"class":146},", [])\n",[136,910,911,913,915],{"class":138,"line":461},[136,912,365],{"class":142},[136,914,404],{"class":142},[136,916,917],{"class":146}," messages:\n",[136,919,920],{"class":138,"line":469},[136,921,888],{"class":142},[136,923,924],{"class":138,"line":479},[136,925,215],{"emptyLinePlaceholder":214},[136,927,928,931,934,937],{"class":138,"line":498},[136,929,930],{"class":142}," for",[136,932,933],{"class":146}," msg ",[136,935,936],{"class":142},"in",[136,938,917],{"class":146},[136,940,941,943],{"class":138,"line":518},[136,942,420],{"class":142},[136,944,423],{"class":146},[136,946,947,950,952],{"class":138,"line":533},[136,948,949],{"class":146}," raw_msg ",[136,951,228],{"class":142},[136,953,954],{"class":146}," service.users().messages().get(\n",[136,956,957,959,961,963,965,968,970,973,976,979,981,983],{"class":138,"line":559},[136,958,808],{"class":224},[136,960,228],{"class":142},[136,962,813],{"class":246},[136,964,238],{"class":146},[136,966,967],{"class":224},"id",[136,969,228],{"class":142},[136,971,972],{"class":146},"msg[",[136,974,975],{"class":246},"\"id\"",[136,977,978],{"class":146},"], ",[136,980,241],{"class":224},[136,982,228],{"class":142},[136,984,985],{"class":246},"\"raw\"\n",[136,987,988],{"class":138,"line":570},[136,989,851],{"class":146},[136,991,992],{"class":138,"line":580},[136,993,630],{"class":146},[136,995,996],{"class":138,"line":589},[136,997,999],{"class":998},"sJ8bj"," # Gmail uses base64url encoding; standard b64decode will fail on padding\n",[136,1001,1002,1005,1007,1010,1013,1016,1019,1022,1025,1028,1031,1034,1037,1040,1042,1045,1048,1051],{"class":138,"line":594},[136,1003,1004],{"class":146}," msg_bytes ",[136,1006,228],{"class":142},[136,1008,1009],{"class":146}," base64.urlsafe_b64decode(raw_msg[",[136,1011,1012],{"class":246},"\"raw\"",[136,1014,1015],{"class":146},"] ",[136,1017,1018],{"class":142},"+",[136,1020,1021],{"class":246}," \"=\"",[136,1023,1024],{"class":142}," *",[136,1026,1027],{"class":146}," (",[136,1029,1030],{"class":234},"4",[136,1032,1033],{"class":142}," -",[136,1035,1036],{"class":234}," len",[136,1038,1039],{"class":146},"(raw_msg[",[136,1041,1012],{"class":246},[136,1043,1044],{"class":146},"]) ",[136,1046,1047],{"class":142},"%",[136,1049,1050],{"class":234}," 4",[136,1052,1053],{"class":146},"))\n",[136,1055,1056,1059,1061],{"class":138,"line":621},[136,1057,1058],{"class":146}," email_obj ",[136,1060,228],{"class":142},[136,1062,1063],{"class":146}," message_from_bytes(msg_bytes)\n",[136,1065,1066],{"class":138,"line":627},[136,1067,630],{"class":146},[136,1069,1070],{"class":138,"line":633},[136,1071,1072],{"class":146}," messages_data.append({\n",[136,1074,1076,1079,1082,1084],{"class":138,"line":1075},38,[136,1077,1078],{"class":246}," \"id\"",[136,1080,1081],{"class":146},": msg[",[136,1083,975],{"class":246},[136,1085,1086],{"class":146},"],\n",[136,1088,1090,1093,1096,1099,1101,1104],{"class":138,"line":1089},39,[136,1091,1092],{"class":246}," \"subject\"",[136,1094,1095],{"class":146},": email_obj.get(",[136,1097,1098],{"class":246},"\"Subject\"",[136,1100,238],{"class":146},[136,1102,1103],{"class":246},"\"No Subject\"",[136,1105,1106],{"class":146},"),\n",[136,1108,1110,1113,1115,1118,1120,1123],{"class":138,"line":1109},40,[136,1111,1112],{"class":246}," \"from\"",[136,1114,1095],{"class":146},[136,1116,1117],{"class":246},"\"From\"",[136,1119,238],{"class":146},[136,1121,1122],{"class":246},"\"Unknown\"",[136,1124,1106],{"class":146},[136,1126,1128,1131,1133,1136,1138,1140],{"class":138,"line":1127},41,[136,1129,1130],{"class":246}," \"date\"",[136,1132,1095],{"class":146},[136,1134,1135],{"class":246},"\"Date\"",[136,1137,238],{"class":146},[136,1139,1122],{"class":246},[136,1141,1106],{"class":146},[136,1143,1145,1148,1150,1153,1155,1158],{"class":138,"line":1144},42,[136,1146,1147],{"class":246}," \"snippet\"",[136,1149,1095],{"class":146},[136,1151,1152],{"class":246},"\"X-Gmail-Snippet\"",[136,1154,238],{"class":146},[136,1156,1157],{"class":246},"\"\"",[136,1159,266],{"class":146},[136,1161,1163],{"class":138,"line":1162},43,[136,1164,1165],{"class":146}," })\n",[136,1167,1169,1171,1173,1175],{"class":138,"line":1168},44,[136,1170,521],{"class":142},[136,1172,858],{"class":146},[136,1174,527],{"class":142},[136,1176,863],{"class":146},[136,1178,1180,1183,1185,1188,1190,1192,1195,1198,1200,1203,1205,1207,1209,1211],{"class":138,"line":1179},45,[136,1181,1182],{"class":146}," logging.warning(",[136,1184,539],{"class":142},[136,1186,1187],{"class":246},"\"Skipping message ",[136,1189,545],{"class":234},[136,1191,972],{"class":146},[136,1193,1194],{"class":246},"'id'",[136,1196,1197],{"class":146},"]",[136,1199,551],{"class":234},[136,1201,1202],{"class":246}," due to fetch error: ",[136,1204,545],{"class":234},[136,1206,877],{"class":146},[136,1208,551],{"class":234},[136,1210,247],{"class":246},[136,1212,266],{"class":146},[136,1214,1216],{"class":138,"line":1215},46,[136,1217,1218],{"class":142}," continue\n",[136,1220,1222],{"class":138,"line":1221},47,[136,1223,215],{"emptyLinePlaceholder":214},[136,1225,1227,1229,1231,1233,1236],{"class":138,"line":1226},48,[136,1228,769],{"class":146},[136,1230,228],{"class":142},[136,1232,902],{"class":146},[136,1234,1235],{"class":246},"\"nextPageToken\"",[136,1237,266],{"class":146},[136,1239,1241,1243,1245],{"class":138,"line":1240},49,[136,1242,365],{"class":142},[136,1244,404],{"class":142},[136,1246,1247],{"class":146}," page_token:\n",[136,1249,1251],{"class":138,"line":1250},50,[136,1252,888],{"class":142},[136,1254,1256],{"class":138,"line":1255},51,[136,1257,215],{"emptyLinePlaceholder":214},[136,1259,1261,1263],{"class":138,"line":1260},52,[136,1262,583],{"class":142},[136,1264,1265],{"class":146}," messages_data\n",[14,1267,1268],{},[54,1269,643],{},[18,1271,1272,1289,1298],{},[21,1273,1274,1275,1278,1279,238,1282,1285,1286,62],{},"The ",[59,1276,1277],{},"q"," parameter drastically reduces payload size and API calls. Use ",[59,1280,1281],{},"from:domain.com",[59,1283,1284],{},"has:attachment",", or ",[59,1287,1288],{},"after:YYYY\u002FMM\u002FDD",[21,1290,1291,1292,1294,1295,62],{},"Always append padding (",[59,1293,228],{},") before decoding to prevent ",[59,1296,1297],{},"binascii.Error",[21,1299,1300,1301,1304,1305,1308],{},"Fallback gracefully to ",[59,1302,1303],{},"email_obj.get()"," to avoid ",[59,1306,1307],{},"KeyError"," on malformed headers.",[40,1310,1312],{"id":1311},"programmatic-sending-draft-creation","Programmatic Sending & Draft Creation",[14,1314,1315,1316,1319],{},"Construct RFC 2822 compliant messages and dispatch via ",[59,1317,1318],{},"users.messages.send",". Differentiate between immediate dispatch and draft staging for user review workflows.",[127,1321,1323],{"className":129,"code":1322,"language":131,"meta":132,"style":132},"from email.mime.text import MIMEText\nfrom email.mime.multipart import MIMEMultipart\nimport base64\nimport time\nimport logging\nfrom googleapiclient.errors import HttpError\n\ndef create_message(sender: str, to: str, subject: str, body: str) -> dict:\n msg = MIMEMultipart(\"alternative\")\n msg[\"to\"] = to\n msg[\"from\"] = sender\n msg[\"subject\"] = subject\n msg.attach(MIMEText(body, \"plain\"))\n return {\"raw\": base64.urlsafe_b64encode(msg.as_bytes()).decode()}\n\ndef send_email_with_retry(service, sender: str, to: str, subject: str, body: str, retries: int = 3):\n message = create_message(sender, to, subject, body)\n \n for attempt in range(retries):\n try:\n result = service.users().messages().send(\n userId=\"me\", body=message\n ).execute()\n logging.info(f\"Email sent successfully. Message ID: {result['id']}\")\n return result\n except HttpError as err:\n status = err.resp.status\n if status in (403, 500, 502, 503):\n wait_time = (2 ** attempt) + (0.1 * (attempt + 1))\n logging.warning(f\"Transient error {status}. Retrying in {wait_time:.2f}s...\")\n time.sleep(wait_time)\n elif status == 429:\n logging.error(\"Rate limit exceeded. Implementing extended backoff.\")\n time.sleep(60)\n else:\n logging.error(f\"Unrecoverable error sending email: {err}\")\n raise\n raise RuntimeError(\"Max retries exceeded for email dispatch.\")\n",[59,1324,1325,1337,1349,1355,1362,1368,1378,1382,1418,1432,1447,1461,1475,1485,1497,1501,1538,1548,1552,1567,1573,1583,1601,1605,1629,1636,1646,1656,1686,1723,1757,1762,1777,1786,1796,1802,1821,1826],{"__ignoreMap":132},[136,1326,1327,1329,1332,1334],{"class":138,"line":139},[136,1328,161],{"class":142},[136,1330,1331],{"class":146}," email.mime.text ",[136,1333,143],{"class":142},[136,1335,1336],{"class":146}," MIMEText\n",[136,1338,1339,1341,1344,1346],{"class":138,"line":150},[136,1340,161],{"class":142},[136,1342,1343],{"class":146}," email.mime.multipart ",[136,1345,143],{"class":142},[136,1347,1348],{"class":146}," MIMEMultipart\n",[136,1350,1351,1353],{"class":138,"line":158},[136,1352,143],{"class":142},[136,1354,683],{"class":146},[136,1356,1357,1359],{"class":138,"line":172},[136,1358,143],{"class":142},[136,1360,1361],{"class":146}," time\n",[136,1363,1364,1366],{"class":138,"line":185},[136,1365,143],{"class":142},[136,1367,155],{"class":146},[136,1369,1370,1372,1374,1376],{"class":138,"line":198},[136,1371,161],{"class":142},[136,1373,720],{"class":146},[136,1375,143],{"class":142},[136,1377,725],{"class":146},[136,1379,1380],{"class":138,"line":211},[136,1381,215],{"emptyLinePlaceholder":214},[136,1383,1384,1386,1389,1392,1395,1398,1400,1403,1405,1408,1410,1413,1416],{"class":138,"line":218},[136,1385,341],{"class":142},[136,1387,1388],{"class":344}," create_message",[136,1390,1391],{"class":146},"(sender: ",[136,1393,1394],{"class":234},"str",[136,1396,1397],{"class":146},", to: ",[136,1399,1394],{"class":234},[136,1401,1402],{"class":146},", subject: ",[136,1404,1394],{"class":234},[136,1406,1407],{"class":146},", body: ",[136,1409,1394],{"class":234},[136,1411,1412],{"class":146},") -> ",[136,1414,1415],{"class":234},"dict",[136,1417,423],{"class":146},[136,1419,1420,1422,1424,1427,1430],{"class":138,"line":269},[136,1421,933],{"class":146},[136,1423,228],{"class":142},[136,1425,1426],{"class":146}," MIMEMultipart(",[136,1428,1429],{"class":246},"\"alternative\"",[136,1431,266],{"class":146},[136,1433,1434,1437,1440,1442,1444],{"class":138,"line":274},[136,1435,1436],{"class":146}," msg[",[136,1438,1439],{"class":246},"\"to\"",[136,1441,1015],{"class":146},[136,1443,228],{"class":142},[136,1445,1446],{"class":146}," to\n",[136,1448,1449,1451,1454,1456,1458],{"class":138,"line":292},[136,1450,1436],{"class":146},[136,1452,1453],{"class":246},"\"from\"",[136,1455,1015],{"class":146},[136,1457,228],{"class":142},[136,1459,1460],{"class":146}," sender\n",[136,1462,1463,1465,1468,1470,1472],{"class":138,"line":313},[136,1464,1436],{"class":146},[136,1466,1467],{"class":246},"\"subject\"",[136,1469,1015],{"class":146},[136,1471,228],{"class":142},[136,1473,1474],{"class":146}," subject\n",[136,1476,1477,1480,1483],{"class":138,"line":333},[136,1478,1479],{"class":146}," msg.attach(MIMEText(body, ",[136,1481,1482],{"class":246},"\"plain\"",[136,1484,1053],{"class":146},[136,1486,1487,1489,1492,1494],{"class":138,"line":338},[136,1488,583],{"class":142},[136,1490,1491],{"class":146}," {",[136,1493,1012],{"class":246},[136,1495,1496],{"class":146},": base64.urlsafe_b64encode(msg.as_bytes()).decode()}\n",[136,1498,1499],{"class":138,"line":351},[136,1500,215],{"emptyLinePlaceholder":214},[136,1502,1503,1505,1508,1511,1513,1515,1517,1519,1521,1523,1525,1528,1531,1533,1536],{"class":138,"line":362},[136,1504,341],{"class":142},[136,1506,1507],{"class":344}," send_email_with_retry",[136,1509,1510],{"class":146},"(service, sender: ",[136,1512,1394],{"class":234},[136,1514,1397],{"class":146},[136,1516,1394],{"class":234},[136,1518,1402],{"class":146},[136,1520,1394],{"class":234},[136,1522,1407],{"class":146},[136,1524,1394],{"class":234},[136,1526,1527],{"class":146},", retries: ",[136,1529,1530],{"class":234},"int",[136,1532,280],{"class":142},[136,1534,1535],{"class":234}," 3",[136,1537,373],{"class":146},[136,1539,1540,1543,1545],{"class":138,"line":376},[136,1541,1542],{"class":146}," message ",[136,1544,228],{"class":142},[136,1546,1547],{"class":146}," create_message(sender, to, subject, body)\n",[136,1549,1550],{"class":138,"line":394},[136,1551,630],{"class":146},[136,1553,1554,1556,1559,1561,1564],{"class":138,"line":399},[136,1555,930],{"class":142},[136,1557,1558],{"class":146}," attempt ",[136,1560,936],{"class":142},[136,1562,1563],{"class":234}," range",[136,1565,1566],{"class":146},"(retries):\n",[136,1568,1569,1571],{"class":138,"line":417},[136,1570,420],{"class":142},[136,1572,423],{"class":146},[136,1574,1575,1578,1580],{"class":138,"line":426},[136,1576,1577],{"class":146}," result ",[136,1579,228],{"class":142},[136,1581,1582],{"class":146}," service.users().messages().send(\n",[136,1584,1585,1587,1589,1591,1593,1596,1598],{"class":138,"line":444},[136,1586,808],{"class":224},[136,1588,228],{"class":142},[136,1590,813],{"class":246},[136,1592,238],{"class":146},[136,1594,1595],{"class":224},"body",[136,1597,228],{"class":142},[136,1599,1600],{"class":146},"message\n",[136,1602,1603],{"class":138,"line":455},[136,1604,851],{"class":146},[136,1606,1607,1609,1611,1614,1616,1619,1621,1623,1625,1627],{"class":138,"line":461},[136,1608,447],{"class":146},[136,1610,539],{"class":142},[136,1612,1613],{"class":246},"\"Email sent successfully. Message ID: ",[136,1615,545],{"class":234},[136,1617,1618],{"class":146},"result[",[136,1620,1194],{"class":246},[136,1622,1197],{"class":146},[136,1624,551],{"class":234},[136,1626,247],{"class":246},[136,1628,266],{"class":146},[136,1630,1631,1633],{"class":138,"line":469},[136,1632,583],{"class":142},[136,1634,1635],{"class":146}," result\n",[136,1637,1638,1640,1642,1644],{"class":138,"line":479},[136,1639,521],{"class":142},[136,1641,858],{"class":146},[136,1643,527],{"class":142},[136,1645,863],{"class":146},[136,1647,1648,1651,1653],{"class":138,"line":498},[136,1649,1650],{"class":146}," status ",[136,1652,228],{"class":142},[136,1654,1655],{"class":146}," err.resp.status\n",[136,1657,1658,1660,1662,1664,1666,1669,1671,1674,1676,1679,1681,1684],{"class":138,"line":518},[136,1659,365],{"class":142},[136,1661,1650],{"class":146},[136,1663,936],{"class":142},[136,1665,1027],{"class":146},[136,1667,1668],{"class":234},"403",[136,1670,238],{"class":146},[136,1672,1673],{"class":234},"500",[136,1675,238],{"class":146},[136,1677,1678],{"class":234},"502",[136,1680,238],{"class":146},[136,1682,1683],{"class":234},"503",[136,1685,373],{"class":146},[136,1687,1688,1691,1693,1695,1698,1701,1704,1706,1708,1711,1713,1716,1718,1721],{"class":138,"line":533},[136,1689,1690],{"class":146}," wait_time ",[136,1692,228],{"class":142},[136,1694,1027],{"class":146},[136,1696,1697],{"class":234},"2",[136,1699,1700],{"class":142}," **",[136,1702,1703],{"class":146}," attempt) ",[136,1705,1018],{"class":142},[136,1707,1027],{"class":146},[136,1709,1710],{"class":234},"0.1",[136,1712,1024],{"class":142},[136,1714,1715],{"class":146}," (attempt ",[136,1717,1018],{"class":142},[136,1719,1720],{"class":234}," 1",[136,1722,1053],{"class":146},[136,1724,1725,1727,1729,1732,1734,1737,1739,1742,1744,1747,1750,1752,1755],{"class":138,"line":559},[136,1726,1182],{"class":146},[136,1728,539],{"class":142},[136,1730,1731],{"class":246},"\"Transient error ",[136,1733,545],{"class":234},[136,1735,1736],{"class":146},"status",[136,1738,551],{"class":234},[136,1740,1741],{"class":246},". Retrying in ",[136,1743,545],{"class":234},[136,1745,1746],{"class":146},"wait_time",[136,1748,1749],{"class":142},":.2f",[136,1751,551],{"class":234},[136,1753,1754],{"class":246},"s...\"",[136,1756,266],{"class":146},[136,1758,1759],{"class":138,"line":570},[136,1760,1761],{"class":146}," time.sleep(wait_time)\n",[136,1763,1764,1767,1769,1772,1775],{"class":138,"line":580},[136,1765,1766],{"class":142}," elif",[136,1768,1650],{"class":146},[136,1770,1771],{"class":142},"==",[136,1773,1774],{"class":234}," 429",[136,1776,423],{"class":146},[136,1778,1779,1781,1784],{"class":138,"line":589},[136,1780,536],{"class":146},[136,1782,1783],{"class":246},"\"Rate limit exceeded. Implementing extended backoff.\"",[136,1785,266],{"class":146},[136,1787,1788,1791,1794],{"class":138,"line":594},[136,1789,1790],{"class":146}," time.sleep(",[136,1792,1793],{"class":234},"60",[136,1795,266],{"class":146},[136,1797,1798,1800],{"class":138,"line":621},[136,1799,464],{"class":142},[136,1801,423],{"class":146},[136,1803,1804,1806,1808,1811,1813,1815,1817,1819],{"class":138,"line":627},[136,1805,536],{"class":146},[136,1807,539],{"class":142},[136,1809,1810],{"class":246},"\"Unrecoverable error sending email: ",[136,1812,545],{"class":234},[136,1814,877],{"class":146},[136,1816,551],{"class":234},[136,1818,247],{"class":246},[136,1820,266],{"class":146},[136,1822,1823],{"class":138,"line":633},[136,1824,1825],{"class":142}," raise\n",[136,1827,1828,1831,1834,1836,1839],{"class":138,"line":1075},[136,1829,1830],{"class":142}," raise",[136,1832,1833],{"class":234}," RuntimeError",[136,1835,603],{"class":146},[136,1837,1838],{"class":246},"\"Max retries exceeded for email dispatch.\"",[136,1840,266],{"class":146},[14,1842,1843],{},[54,1844,643],{},[18,1846,1847,1853,1864],{},[21,1848,1849,1852],{},[59,1850,1851],{},"MIMEMultipart(\"alternative\")"," ensures proper fallback rendering across clients.",[21,1854,1855,1856,1859,1860,1863],{},"Exponential backoff with jitter handles transient ",[59,1857,1858],{},"5xx"," and ",[59,1861,1862],{},"429"," errors without crashing the pipeline.",[21,1865,1866,1867,1870,1871,1874],{},"Use ",[59,1868,1869],{},"users().drafts().create()"," instead of ",[59,1872,1873],{},".send()"," when building approval queues or scheduled campaigns.",[40,1876,1878],{"id":1877},"production-hardening-quota-management","Production Hardening & Quota Management",[14,1880,1881],{},"Ensure script resilience under Google's strict rate limits and network instability. The Gmail API enforces a 1,000,000,000 daily quota and 250 QPS burst limit, but practical limits are often lower for standard consumer accounts.",[18,1883,1884,1901,1911,1928,1934],{},[21,1885,1886,1889,1890,1859,1893,1896,1897,1900],{},[54,1887,1888],{},"Monitor Headers:"," Inspect ",[59,1891,1892],{},"X-RateLimit-Remaining",[59,1894,1895],{},"X-RateLimit-Reset"," in response headers. Implement ",[59,1898,1899],{},"time.sleep()"," proportional to remaining quota.",[21,1902,1903,1906,1907,1910],{},[54,1904,1905],{},"Batch Operations:"," Use ",[59,1908,1909],{},"googleapiclient.http.BatchHttpRequest"," to group up to 100 requests into a single HTTP call. This reduces latency and counts as a single quota unit for many operations.",[21,1912,1913,1916,1917,1920,1921,238,1924,1927],{},[54,1914,1915],{},"Structured Logging:"," Wrap every API call in ",[59,1918,1919],{},"try\u002Fexcept googleapiclient.errors.HttpError",". Log ",[59,1922,1923],{},"err.resp.status",[59,1925,1926],{},"err.content",", and request parameters to CloudWatch, Datadog, or local JSON logs.",[21,1929,1930,1933],{},[54,1931,1932],{},"Idempotency & Caching:"," Cache message IDs locally using SQLite or Redis. Skip re-processing messages you've already handled to avoid redundant quota consumption.",[21,1935,1936,1939,1940,1943],{},[54,1937,1938],{},"Timeout Configuration:"," Initialize the service with ",[59,1941,1942],{},"http=httplib2.Http(timeout=30)"," to prevent hanging threads during network degradation.",[40,1945,1947],{"id":1946},"common-mistakes-to-avoid","Common Mistakes to Avoid",[18,1949,1950,1956,1969,1989,1995],{},[21,1951,1952,1955],{},[54,1953,1954],{},"Hardcoding OAuth2 tokens or client secrets in source control",", triggering automatic Google security revocations and account lockouts.",[21,1957,1958,1968],{},[54,1959,1960,1961,109,1964,1967],{},"Using ",[59,1962,1963],{},"format='full'",[59,1965,1966],{},"format='raw'"," on bulk list operations"," instead of fetching lightweight IDs first and paginating, which exhausts quota instantly.",[21,1970,1971,1974,1975,1978,1979,1981,1982,109,1985,1988],{},[54,1972,1973],{},"Ignoring base64url encoding standards"," and using standard ",[59,1976,1977],{},"base64.b64decode()",", causing ",[59,1980,1297],{}," when encountering ",[59,1983,1984],{},"-",[59,1986,1987],{},"_"," characters.",[21,1990,1991,1994],{},[54,1992,1993],{},"Failing to implement exponential backoff for 429 Too Many Requests",", leading to immediate script termination during quota spikes or concurrent execution.",[21,1996,1997,2000,2001,2004,2005,2007],{},[54,1998,1999],{},"Assuming all emails contain HTML bodies"," and not implementing ",[59,2002,2003],{},"text\u002Fplain"," fallback, causing ",[59,2006,1307],{}," on plain-text or multipart\u002Falternative messages.",[40,2009,2011],{"id":2010},"frequently-asked-questions","Frequently Asked Questions",[14,2013,2014,2017,2018,2020,2021,2023,2024,2026],{},[54,2015,2016],{},"How do I handle Gmail API daily quota limits in production?","\nMonitor ",[59,2019,1892],{}," headers and implement exponential backoff with jitter. Use ",[59,2022,1277],{}," filters to reduce payload size, batch requests via ",[59,2025,1909],{},", and cache responses locally to avoid redundant API calls. If hitting limits consistently, request quota increases via Google Cloud Console.",[14,2028,2029,2032],{},[54,2030,2031],{},"Can I use a Service Account instead of OAuth2 for Gmail automation?","\nNo. Google does not support Service Accounts for the Gmail API due to strict user-data privacy policies. You must use OAuth2 with a user account. For headless automation, use the initial interactive flow to generate a refresh token, then store it securely for programmatic token refresh.",[14,2034,2035,2042,2043,2045,2046,1859,2048,2045,2051,2053,2054,2056,2057,2059,2060,2062,2063,2066],{},[54,2036,2037,2038,2041],{},"Why does ",[59,2039,2040],{},"base64.urlsafe_b64decode()"," fail on some email bodies?","\nGmail returns base64url encoded strings, which replace ",[59,2044,1018],{}," with ",[59,2047,1984],{},[59,2049,2050],{},"\u002F",[59,2052,1987],{},". Standard ",[59,2055,1977],{}," expects standard padding. Always use ",[59,2058,2040],{}," and append padding (",[59,2061,228],{},") if ",[59,2064,2065],{},"len(raw) % 4 != 0"," to prevent decoding errors.",[14,2068,2069,2072,2073,2045,2076,2079,2080,2083,2084,2087,2088,2091],{},[54,2070,2071],{},"How do I parse attachments from the Gmail API response?","\nAttachments are nested under ",[59,2074,2075],{},"payload.parts",[59,2077,2078],{},"mimeType"," indicating the file type. Extract the ",[59,2081,2082],{},"attachmentId",", then call ",[59,2085,2086],{},"service.users().messages().attachments().get(userId='me', messageId=msg_id, id=attachment_id).execute()"," to download the ",[59,2089,2090],{},"data"," field, decode it from base64url, and write to disk.",[2093,2094,2095],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":132,"searchDepth":150,"depth":150,"links":2097},[2098,2099,2100,2101,2102,2103,2104],{"id":42,"depth":150,"text":43},{"id":121,"depth":150,"text":122},{"id":667,"depth":150,"text":668},{"id":1311,"depth":150,"text":1312},{"id":1877,"depth":150,"text":1878},{"id":1946,"depth":150,"text":1947},{"id":2010,"depth":150,"text":2011},"md",{},"\u002Fautomating-side-hustle-operations-with-apis\u002Fconnecting-crm-email-apis\u002Fautomate-gmail-with-python-and-gmail-api",{"title":5,"description":16},"automating-side-hustle-operations-with-apis\u002Fconnecting-crm-email-apis\u002Fautomate-gmail-with-python-and-gmail-api\u002Findex","D79Roan4qEJpX-YZ5FAVTvTWpR20V8fM22-xzH-o5is",{"@context":2112,"@type":2113,"mainEntity":2114},"https:\u002F\u002Fschema.org","FAQPage",[2115,2120,2123,2127],{"@type":2116,"name":2016,"acceptedAnswer":2117},"Question",{"@type":2118,"text":2119},"Answer","Monitor X-RateLimit-Remaining headers and implement exponential backoff with jitter. Use q filters to reduce payload size, batch requests via googleapiclient.http.BatchHttpRequest, and cache responses locally to avoid redundant API calls. If hitting limits consistently, request quota increases via Google Cloud Console.",{"@type":2116,"name":2031,"acceptedAnswer":2121},{"@type":2118,"text":2122},"No. Google does not support Service Accounts for the Gmail API due to strict user-data privacy policies. You must use OAuth2 with a user account. For headless automation, use the initial interactive flow to generate a refresh token, then store it securely for programmatic token refresh.",{"@type":2116,"name":2124,"acceptedAnswer":2125},"Why does base64.urlsafe_b64decode() fail on some email bodies?",{"@type":2118,"text":2126},"Gmail returns base64url encoded strings, which replace + with - and \u002F with _. Standard base64.b64decode() expects standard padding. Always use base64.urlsafe_b64decode() and append padding (=) if len(raw) % 4 != 0 to prevent decoding errors.",{"@type":2116,"name":2071,"acceptedAnswer":2128},{"@type":2118,"text":2129},"Attachments are nested under payload.parts with mimeType indicating the file type. Extract the attachmentId, then call service.users().messages().attachments().get(userId='me', messageId=msg_id, id=attachment_id).execute() to download the data field, decode it from base64url, and write to disk.",1778017886128]