How to Build an AI Executive Assistant with Claude Code and Google Workspace
Combine Claude Code with the Google Workspace CLI to build an AI executive assistant that reads emails, creates docs, and manages your calendar automatically.
The Real Cost of Running an Executive’s Calendar, Inbox, and Docs Manually
The average executive spends 23 hours per week in meetings and another 4–5 hours daily on email. That’s before you count the time spent writing follow-up documents, scheduling calls across time zones, or hunting down action items buried in last Tuesday’s thread.
AI executive assistants built on tools like Claude Code and the Google Workspace APIs can handle a meaningful chunk of that work — reading and triaging email, creating calendar events, drafting meeting notes, and generating daily briefings — without a human in the loop.
This guide walks through exactly how to build one. You’ll set up Claude Code to interact with Gmail, Google Calendar, and Google Docs using Google’s official API libraries, then wire up the workflows that make the assistant genuinely useful day-to-day.
The result isn’t a chatbot that answers questions. It’s an autonomous agent that reads your inbox at 7am, drafts responses to routine emails, blocks focus time before important calls, and drops a meeting summary into a shared doc — all from a single command or on a schedule.
What Claude Code Is and Why It Works for This
Claude Code is Anthropic’s terminal-native AI coding agent. You install it via npm, run it from your command line, and give it tasks in natural language. It can read and write files, execute bash commands, search the web, and call external APIs — all autonomously, without needing you to approve each step.
How Claude Code Differs from the Claude Chat Interface
The web interface at claude.ai is conversational. You type, Claude responds, you type again. Claude Code is different: it’s designed to take a goal, break it into steps, execute those steps using real tools, and return a result.
When you say “check my inbox and summarize anything urgent from the last 24 hours,” Claude Code doesn’t just write text. It will:
- Look at what scripts or tools are available in your project
- Run the appropriate Python or bash commands to fetch email data
- Parse the responses
- Format a structured summary
- Optionally take follow-up actions based on what it finds
This makes it fundamentally suited to building an executive assistant — the kind of agent that doesn’t just tell you what to do, but actually does it.
Claude Code’s Tool Execution Model
Claude Code uses Anthropic’s tool use API under the hood. In practice, this means it can call any function you expose to it — a Python script that queries Gmail, a bash script that creates a calendar event, a helper that writes to Google Docs. You define the tools; Claude Code decides when and how to use them.
The key file you’ll work with is CLAUDE.md, a markdown file in your project root that Claude Code reads as its operating instructions. Think of it as the assistant’s job description — it tells Claude Code which tools are available, how to use them, and how to behave in different situations.
Setting Up Google Workspace API Access
Before writing any agent logic, you need to configure access to Google’s APIs. This involves enabling the right services in Google Cloud Console, creating OAuth credentials, and storing an access token your scripts can reuse.
Enable the Required APIs
Start by going to console.cloud.google.com and creating a new project (or using an existing one).
In the left sidebar, go to APIs & Services → Library and enable each of the following:
- Gmail API — for reading and sending email
- Google Calendar API — for reading and creating calendar events
- Google Docs API — for creating and editing documents
- Google Drive API — for file management and sharing
Each requires a separate enable step. Click each one in the library, then click Enable.
Configure OAuth 2.0 Credentials
Go to APIs & Services → Credentials and click Create Credentials → OAuth client ID.
Set the application type to Desktop app. Give it a name (e.g., “Executive Assistant”), then click Create. Download the resulting JSON file and save it to your project root as credentials.json.
You’ll also need to configure the OAuth consent screen. Under APIs & Services → OAuth consent screen, set the user type to External (even if you’re the only user), fill in the app name and your email, then add the following scopes manually:
https://www.googleapis.com/auth/gmail.modify
https://www.googleapis.com/auth/calendar
https://www.googleapis.com/auth/documents
https://www.googleapis.com/auth/drive
Add yourself as a test user so the app works without going through Google’s verification process.
Install Dependencies and Run Initial Auth
Create a Python virtual environment in your project folder and install the required packages:
python3 -m venv .venv
source .venv/bin/activate
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client python-dateutil
Create an auth.py file to handle credential generation:
import os
import json
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
SCOPES = [
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/calendar',
'https://www.googleapis.com/auth/documents',
'https://www.googleapis.com/auth/drive',
]
def get_credentials():
creds = None
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
with open('token.json', 'w') as token:
token.write(creds.to_json())
return creds
if __name__ == '__main__':
get_credentials()
print("Authentication successful. token.json created.")
Run it once:
python auth.py
This opens a browser window, asks you to sign in, and creates a token.json file. From here on, your scripts use that token automatically. Add token.json and credentials.json to .gitignore — never commit these.
Building the Core Integration Layer
With auth handled, you can build the helper scripts that Claude Code will call. Each script is a standalone Python file that accepts command-line arguments and returns JSON output. This makes them easy for Claude Code to invoke and parse.
The Gmail Helper
Create gmail_helper.py:
import sys
import json
import base64
import argparse
from datetime import datetime, timedelta
from googleapiclient.discovery import build
from auth import get_credentials
def get_service():
creds = get_credentials()
return build('gmail', 'v1', credentials=creds)
def list_recent_messages(hours=24, max_results=20, label='INBOX'):
service = get_service()
after_timestamp = int((datetime.now() - timedelta(hours=hours)).timestamp())
query = f'after:{after_timestamp} in:{label}'
result = service.users().messages().list(
userId='me',
q=query,
maxResults=max_results
).execute()
messages = result.get('messages', [])
summaries = []
for msg in messages:
full_msg = service.users().messages().get(
userId='me',
id=msg['id'],
format='metadata',
metadataHeaders=['From', 'Subject', 'Date']
).execute()
headers = {h['name']: h['value'] for h in full_msg['payload']['headers']}
snippet = full_msg.get('snippet', '')
summaries.append({
'id': msg['id'],
'from': headers.get('From', ''),
'subject': headers.get('Subject', ''),
'date': headers.get('Date', ''),
'snippet': snippet,
'labels': full_msg.get('labelIds', [])
})
return summaries
def get_message_body(message_id):
service = get_service()
msg = service.users().messages().get(
userId='me',
id=message_id,
format='full'
).execute()
def decode_part(part):
if part.get('mimeType') == 'text/plain':
data = part['body'].get('data', '')
return base64.urlsafe_b64decode(data).decode('utf-8', errors='ignore')
elif part.get('parts'):
for subpart in part['parts']:
result = decode_part(subpart)
if result:
return result
return ''
body = decode_part(msg['payload'])
headers = {h['name']: h['value'] for h in msg['payload']['headers']}
return {
'id': message_id,
'from': headers.get('From', ''),
'subject': headers.get('Subject', ''),
'date': headers.get('Date', ''),
'body': body
}
def send_email(to, subject, body, thread_id=None):
service = get_service()
message_text = f"To: {to}\nSubject: {subject}\n\n{body}"
encoded = base64.urlsafe_b64encode(message_text.encode()).decode()
message = {'raw': encoded}
if thread_id:
message['threadId'] = thread_id
result = service.users().messages().send(userId='me', body=message).execute()
return {'status': 'sent', 'id': result['id']}
def mark_as_read(message_id):
service = get_service()
service.users().messages().modify(
userId='me',
id=message_id,
body={'removeLabelIds': ['UNREAD']}
).execute()
return {'status': 'marked_read', 'id': message_id}
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('action', choices=['list', 'read', 'send', 'mark_read'])
parser.add_argument('--hours', type=int, default=24)
parser.add_argument('--max', type=int, default=20)
parser.add_argument('--id', type=str)
parser.add_argument('--to', type=str)
parser.add_argument('--subject', type=str)
parser.add_argument('--body', type=str)
parser.add_argument('--thread', type=str)
args = parser.parse_args()
if args.action == 'list':
result = list_recent_messages(hours=args.hours, max_results=args.max)
elif args.action == 'read':
result = get_message_body(args.id)
elif args.action == 'send':
result = send_email(args.to, args.subject, args.body, args.thread)
elif args.action == 'mark_read':
result = mark_as_read(args.id)
print(json.dumps(result, indent=2))
Test it from the command line:
python gmail_helper.py list --hours 24 --max 10
You should get a JSON array of your recent emails.
The Calendar Helper
Create calendar_helper.py:
import sys
import json
import argparse
from datetime import datetime, timedelta
from googleapiclient.discovery import build
from auth import get_credentials
def get_service():
creds = get_credentials()
return build('calendar', 'v3', credentials=creds)
def list_events(days_ahead=7, calendar_id='primary'):
service = get_service()
now = datetime.utcnow().isoformat() + 'Z'
end = (datetime.utcnow() + timedelta(days=days_ahead)).isoformat() + 'Z'
events_result = service.events().list(
calendarId=calendar_id,
timeMin=now,
timeMax=end,
maxResults=50,
singleEvents=True,
orderBy='startTime'
).execute()
events = events_result.get('items', [])
formatted = []
for event in events:
start = event['start'].get('dateTime', event['start'].get('date'))
end_time = event['end'].get('dateTime', event['end'].get('date'))
formatted.append({
'id': event.get('id'),
'summary': event.get('summary', 'No title'),
'start': start,
'end': end_time,
'description': event.get('description', ''),
'attendees': [a['email'] for a in event.get('attendees', [])],
'location': event.get('location', ''),
'hangout_link': event.get('hangoutLink', '')
})
return formatted
def create_event(summary, start_time, end_time, description='', attendees=None, location=''):
service = get_service()
event = {
'summary': summary,
'location': location,
'description': description,
'start': {'dateTime': start_time, 'timeZone': 'America/New_York'},
'end': {'dateTime': end_time, 'timeZone': 'America/New_York'},
}
if attendees:
event['attendees'] = [{'email': email} for email in attendees]
event['conferenceData'] = {
'createRequest': {'requestId': f'meet-{start_time}'}
}
result = service.events().insert(
calendarId='primary',
body=event,
conferenceDataVersion=1 if attendees else 0,
sendUpdates='all' if attendees else 'none'
).execute()
return {
'status': 'created',
'id': result['id'],
'link': result.get('htmlLink'),
'meet_link': result.get('hangoutLink', '')
}
def find_free_slots(duration_minutes=60, days_ahead=5):
service = get_service()
now = datetime.utcnow()
end_window = now + timedelta(days=days_ahead)
body = {
"timeMin": now.isoformat() + 'Z',
"timeMax": end_window.isoformat() + 'Z',
"items": [{"id": "primary"}]
}
freebusy = service.freebusy().query(body=body).execute()
busy_times = freebusy['calendars']['primary']['busy']
# Generate candidate slots (9am-5pm, Mon-Fri)
slots = []
current = now.replace(hour=9, minute=0, second=0, microsecond=0)
if current < now:
current += timedelta(days=1)
while current < end_window:
if current.weekday() < 5: # Weekdays only
slot_end = current + timedelta(minutes=duration_minutes)
if slot_end.hour <= 17:
# Check if overlaps with any busy period
is_free = True
for busy in busy_times:
busy_start = datetime.fromisoformat(busy['start'].replace('Z', '+00:00'))
busy_end = datetime.fromisoformat(busy['end'].replace('Z', '+00:00'))
if not (slot_end <= busy_start.replace(tzinfo=None) or
current >= busy_end.replace(tzinfo=None)):
is_free = False
break
if is_free:
slots.append({
'start': current.isoformat(),
'end': slot_end.isoformat()
})
current += timedelta(minutes=30)
return slots[:10] # Return first 10 available slots
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('action', choices=['list', 'create', 'slots'])
parser.add_argument('--days', type=int, default=7)
parser.add_argument('--summary', type=str)
parser.add_argument('--start', type=str)
parser.add_argument('--end', type=str)
parser.add_argument('--description', type=str, default='')
parser.add_argument('--attendees', type=str, default='')
parser.add_argument('--location', type=str, default='')
parser.add_argument('--duration', type=int, default=60)
args = parser.parse_args()
if args.action == 'list':
result = list_events(days_ahead=args.days)
elif args.action == 'create':
attendee_list = [e.strip() for e in args.attendees.split(',') if e.strip()]
result = create_event(args.summary, args.start, args.end,
args.description, attendee_list, args.location)
elif args.action == 'slots':
result = find_free_slots(duration_minutes=args.duration, days_ahead=args.days)
print(json.dumps(result, indent=2))
The Google Docs Helper
Create docs_helper.py:
import sys
import json
import argparse
from googleapiclient.discovery import build
from auth import get_credentials
def get_services():
creds = get_credentials()
docs_service = build('docs', 'v1', credentials=creds)
drive_service = build('drive', 'v3', credentials=creds)
return docs_service, drive_service
def create_document(title, content, share_with=None):
docs_service, drive_service = get_services()
# Create empty document
doc = docs_service.documents().create(body={'title': title}).execute()
doc_id = doc['documentId']
# Insert content
requests = [
{
'insertText': {
'location': {'index': 1},
'text': content
}
}
]
docs_service.documents().batchUpdate(
documentId=doc_id,
body={'requests': requests}
).execute()
# Share if needed
if share_with:
for email in share_with:
drive_service.permissions().create(
fileId=doc_id,
body={
'type': 'user',
'role': 'writer',
'emailAddress': email
},
sendNotificationEmail=True
).execute()
return {
'status': 'created',
'id': doc_id,
'title': title,
'url': f'https://docs.google.com/document/d/{doc_id}/edit'
}
def append_to_document(doc_id, content):
docs_service, _ = get_services()
doc = docs_service.documents().get(documentId=doc_id).execute()
end_index = doc['body']['content'][-1]['endIndex'] - 1
requests = [
{
'insertText': {
'location': {'index': end_index},
'text': '\n' + content
}
}
]
docs_service.documents().batchUpdate(
documentId=doc_id,
body={'requests': requests}
).execute()
return {'status': 'appended', 'id': doc_id}
def list_recent_docs(max_results=10):
_, drive_service = get_services()
results = drive_service.files().list(
q="mimeType='application/vnd.google-apps.document'",
orderBy='modifiedTime desc',
maxResults=max_results,
fields='files(id, name, modifiedTime, webViewLink)'
).execute()
return results.get('files', [])
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('action', choices=['create', 'append', 'list'])
parser.add_argument('--title', type=str)
parser.add_argument('--content', type=str)
parser.add_argument('--id', type=str)
parser.add_argument('--share', type=str, default='')
args = parser.parse_args()
if args.action == 'create':
share_list = [e.strip() for e in args.share.split(',') if e.strip()]
result = create_document(args.title, args.content, share_list or None)
elif args.action == 'append':
result = append_to_document(args.id, args.content)
elif args.action == 'list':
result = list_recent_docs()
print(json.dumps(result, indent=2))
Configuring Claude Code to Use Your Tools
With the helper scripts in place, the next step is telling Claude Code how to use them. This is where CLAUDE.md becomes essential.
Writing Your CLAUDE.md
Create a CLAUDE.md file in your project root:
# Executive Assistant Configuration
## Available Tools
### Gmail
Use `python gmail_helper.py` to interact with email.
**List recent emails:**
```bash
python gmail_helper.py list --hours 24 --max 20
Read full email:
python gmail_helper.py read --id <message_id>
Send email:
python gmail_helper.py send --to email@domain.com --subject "Subject" --body "Body text"
Mark as read:
python gmail_helper.py mark_read --id <message_id>
Calendar
Use python calendar_helper.py to manage calendar events.
List upcoming events:
python calendar_helper.py list --days 7
Create event:
python calendar_helper.py create --summary "Meeting Name" --start "2025-02-15T14:00:00" --end "2025-02-15T15:00:00" --attendees "person@company.com,other@company.com"
Find free slots:
python calendar_helper.py slots --duration 60 --days 5
Google Docs
Use python docs_helper.py to manage documents.
Create new document:
python docs_helper.py create --title "Document Title" --content "Document content here"
Append to existing document:
python docs_helper.py append --id <doc_id> --content "New content to add"
Behavioral Guidelines
- Always check email before reporting on inbox status
- When creating calendar events, confirm details before sending invites
- When summarizing emails, group by urgency: Action Required > FYI > Newsletter/Promo
- Meeting notes should include: date, attendees, key decisions, action items with owners
- Never delete emails or calendar events without explicit confirmation
- Use ISO 8601 format for all datetime values
- Default timezone is America/New_York unless told otherwise
This file is loaded automatically when Claude Code starts in this directory. It gives the assistant a precise map of what's available and how to use it.
### Installing and Running Claude Code
If you haven't already, install Claude Code globally:
```bash
npm install -g @anthropic-ai/claude-code
Then start it in your project directory:
cd your-assistant-project
claude
You’ll see a prompt. Try a simple test:
What emails did I receive in the last 24 hours?
Claude Code will call python gmail_helper.py list --hours 24, parse the JSON output, and give you a structured summary. If it works, you have a functional foundation.
Building the Executive Assistant Features
With the infrastructure working, you can now build the specific workflows that make this assistant genuinely useful.
Inbox Triage and Email Summaries
The most immediately valuable feature is a morning inbox triage. Run this command each morning:
Triage my inbox from the last 24 hours. For each email: categorize it as Action Required, Waiting for Reply, FYI, or Promotional. For anything Action Required, summarize what needs to happen and by when if there's a deadline mentioned.
Claude Code will:
- Run
gmail_helper.py list --hours 24 --max 30 - For emails with important-looking subjects or unknown senders, call
gmail_helper.py readto get the full body - Categorize each message based on its content
- Return a structured triage report
You can also ask it to draft responses to routine emails:
Draft a reply to the email from Sarah Chen about the Q4 budget review. Tell her I can meet this week, Thursday or Friday afternoon, and ask her to send the agenda in advance.
Claude Code reads the thread, generates a contextually appropriate reply, and — if you confirm — sends it via gmail_helper.py send.
Calendar Management and Scheduling
Calendar operations are where agentic behavior really shines. Instead of manually checking availability, you can say:
I need to schedule a 45-minute intro call with Marcus Webb (marcus@example.com) sometime next week. Find a slot that works and send him an invite.
Claude Code will:
- Run
calendar_helper.py list --days 7to see your existing commitments - Run
calendar_helper.py slots --duration 45 --days 7to find free windows - Pick a reasonable time (avoiding early morning back-to-backs or late Friday slots, based on your preferences in CLAUDE.md)
- Run
calendar_helper.py createwith the attendee’s email and the chosen time - Confirm what it did
For more complex scheduling, you can add preferences to CLAUDE.md:
## Scheduling Preferences
- No meetings before 9:30am or after 5:00pm
- Protect Tuesdays 2-5pm for deep work
- Always add a 15-minute buffer after calls with more than 3 attendees
- Prefer back-to-back meetings in the morning, focused blocks in the afternoon
Claude Code will factor these in when finding available times.
Document Creation and Meeting Notes
One of the most tedious parts of an executive’s day is turning meeting notes into shareable documents. You can automate a large portion of this.
First, add a step to your post-meeting routine:
I just finished the product roadmap review with Jamie Torres, Alex Kim, and Priya Patel. Key decisions: we're pushing the v2.0 launch from March to April 15th, cutting the legacy export feature from scope, and doubling down on the mobile API. Action items: Jamie owns the revised timeline by Friday, Alex needs to update the spec doc, Priya will notify the enterprise clients about the delay.
Create a meeting notes doc, title it "Product Roadmap Review - [today's date]", and share it with jamie@company.com, alex@company.com, and priya@company.com.
Claude Code will format the notes with proper sections (attendees, decisions, action items with owners and deadlines), create the Google Doc, and share it with the relevant people.
You can also automate pre-meeting document creation:
I have a board call tomorrow at 2pm. Pull my calendar events from this week and create a brief status update doc covering what was completed, any blockers, and key decisions made. Title it "Board Call Prep - [date]".
This pulls your calendar data, generates a structured update, and creates the doc — ready for you to review and add context before the call.
Daily Briefing Automation
The highest-value workflow is a daily briefing that pulls everything together. You can run this with a single command each morning, or set it up as a cron job.
Create a file called daily_briefing.md in your project root with this prompt:
Good morning. Please prepare my daily briefing:
1. EMAIL SUMMARY: List all emails from the last 12 hours. Flag anything that needs a same-day response. Note anything that's been waiting on my reply for more than 48 hours.
2. TODAY'S CALENDAR: List all meetings today with times, attendees, and any Google Meet links. Flag any that have no agenda or description — those likely need prep.
3. THIS WEEK AHEAD: Show the next 5 days of calendar. Identify any scheduling conflicts or days that look overloaded.
4. PRIORITY: Based on emails and calendar, list the top 3 things I should focus on today.
Format this as clean, scannable text — no unnecessary headers, bullet points where appropriate.
Then create a shell script briefing.sh:
#!/bin/bash
cd /path/to/your/assistant-project
source .venv/bin/activate
claude --print "$(cat daily_briefing.md)"
And add it to your crontab to run at 7:30am:
crontab -e
# Add:
30 7 * * 1-5 /path/to/briefing.sh >> ~/briefings/$(date +\%Y-\%m-\%d).txt 2>&1
The --print flag runs Claude Code non-interactively, executing the prompt and writing the output to stdout. Your daily briefing lands in a text file waiting for you each weekday morning.
Advanced Patterns and Workflows
Once the basics are solid, there are several patterns that significantly extend what the assistant can do.
Chaining Actions Across Tools
The real value of Claude Code as an executive assistant comes from multi-step tasks that cross tool boundaries. Some examples that work well in practice:
The meeting-to-action workflow:
Look at my calendar for the past week. For any meeting that doesn't have a corresponding document in Drive from the same day, create a meeting notes template doc with the correct title, attendees, and date pre-filled.
The follow-up workflow:
Find emails in my inbox where I said I'd follow up "next week" or "in a few days" and that date has now passed. Draft follow-up emails for each one.
The scheduling-with-context workflow:
I need to reschedule tomorrow's 3pm with David Chen. Check if there's a more recent email from him about the topic, then find three alternative times this week and draft an email to him with the options.
These multi-step tasks work because Claude Code maintains context across tool calls. It reads the email to understand the topic, checks the calendar for availability, and drafts the message with full context — all in one shot.
Handling Edge Cases and Failures
Build resilience into your helpers. The Gmail API rate limits are generous (250 quota units per user per second), but you’ll occasionally hit token expiry or network issues. Add retry logic to auth.py:
from google.auth.exceptions import TransportError
import time
def get_credentials_with_retry(max_retries=3):
for attempt in range(max_retries):
try:
return get_credentials()
except TransportError as e:
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
else:
raise
Also add error handling in your helpers that returns structured JSON even on failure:
try:
result = list_recent_messages(hours=args.hours)
print(json.dumps(result))
except Exception as e:
print(json.dumps({'error': str(e), 'status': 'failed'}))
sys.exit(1)
When Claude Code receives an error JSON, it can retry, try an alternative approach, or ask for clarification — rather than crashing silently.
Adding Custom Slash Commands
Claude Code supports custom slash commands defined in your project. Create a .claude/commands/ directory:
mkdir -p .claude/commands
Create .claude/commands/triage.md:
Perform an inbox triage for the last 24 hours.
1. List all emails with gmail_helper.py list --hours 24 --max 30
2. For any email with an unknown sender or subject suggesting urgency, read the full body
3. Categorize each email: Action Required | Waiting | FYI | Promo
4. For Action Required items, note the specific action needed
5. Output as a clean numbered list, Action Required items first
Create .claude/commands/standup.md:
Generate a standup update for today.
1. Check calendar for yesterday's meetings
2. Check calendar for today's meetings
3. Check email for any blockers or important updates from the last 18 hours
4. Format as:
- Yesterday: [what was done/discussed]
- Today: [what's planned]
- Blockers: [anything needing attention]
Keep it under 150 words.
Run them with:
/triage
/standup
This gives you repeatable, one-word commands for your most common tasks.
A No-Code Alternative: How MindStudio Handles This
The setup above requires comfort with Python, OAuth flows, Google Cloud Console, and the command line. That’s a reasonable ask for a developer building tooling for an executive — but not for an executive setting this up for themselves.
MindStudio offers a no-code path to the same outcome. It has native Google Workspace integrations built in — you connect your Google account with OAuth once, and you get access to Gmail, Google Calendar, and Google Docs as pre-built capabilities, no scripting required.
You can build the same inbox triage, calendar management, and document creation workflows using MindStudio’s visual builder. The logic is identical: read recent emails, categorize them, draft responses, check availability, create docs. You just drag and drop blocks instead of writing Python.
What makes this especially useful for executive assistant use cases is MindStudio’s support for scheduled background agents. You can build the daily briefing workflow once — connect your Gmail, set up the summary logic, define the output format — and schedule it to run every weekday morning at 7:30am. No cron jobs, no shell scripts, no server to maintain.
MindStudio’s Agent Skills Plugin also means that if you’re building a custom Claude Code agent and want to offload specific tasks (like generating images for slide decks, or running more complex multi-step workflows), you can call MindStudio capabilities directly from Claude Code via their npm SDK. The two approaches complement each other rather than competing.
If you’re a developer building for others or want full control over the implementation, the Claude Code approach is the right fit. If you want to set this up quickly without writing code, MindStudio gets you there faster. You can try it free at mindstudio.ai.
Frequently Asked Questions
Is Claude Code safe to connect to a real executive inbox?
It’s as safe as the scripts you write and the permissions you grant. The OAuth scopes defined in auth.py control what the assistant can do — if you only grant gmail.readonly, it can read but never send. The gmail.modify scope used in the examples above allows reading, sending, and marking as read, but not permanently deleting.
The more important question is: how autonomous do you want it to be? For most use cases, it makes sense to have the assistant draft emails and confirm before sending, rather than sending automatically. You can enforce this in CLAUDE.md by adding a rule: “Never send emails without explicit user confirmation. Always show the draft first.”
For calendar events, similar logic applies. Creating low-stakes personal reminders autonomously is fine. Sending meeting invites to external parties should require a confirmation step.
Does this work with Google Workspace accounts (business) or only personal Gmail?
It works with both. For personal Gmail accounts, the OAuth flow in this guide works as-is. For Google Workspace accounts (accounts under a company domain), your Workspace admin may need to authorize the OAuth app under Admin Console → Security → API Controls → Domain-wide Delegation, or grant internal access in the OAuth consent screen settings.
Check with your IT team before connecting to a corporate account — some organizations restrict third-party API access entirely.
What are the rate limits for Google’s APIs?
Gmail API: 250 quota units per user per second. Most read operations cost 5 units; sends cost 100 units. For a daily briefing that reads 20-30 emails, you’re well within limits.
Google Calendar API: 1,000,000 queries per day per project. For an executive assistant doing a few dozen operations per day, this is effectively unlimited.
Google Docs API: 300 requests per minute per project. Document creation and appending are lightweight operations. You’d have to be doing bulk document generation to approach this limit.
Can Claude Code run these workflows automatically, or does it always need manual prompts?
Claude Code itself requires a prompt to start — it’s not a daemon that runs in the background. For scheduled automation, you use cron (Linux/macOS) or Task Scheduler (Windows) to trigger a shell script that calls Claude Code with --print and a predefined prompt, as shown in the daily briefing section above.
If you want true always-on automation without managing cron jobs, you’ll want a platform that handles scheduling for you. MindStudio’s scheduled agent type handles this out of the box — you define the schedule and the workflow runs on its own.
How do you handle multiple Google accounts for different executives?
Each account needs its own OAuth credentials and token file. The cleanest approach is to parameterize your helpers:
python gmail_helper.py list --token token_ceo.json --credentials credentials_ceo.json
Then use separate CLAUDE.md configurations (or a single one with explicit account references) for each user. You can store credentials in environment variables or a secrets manager like AWS Secrets Manager if you’re deploying this for an organization.
What’s the difference between Claude Code and the Claude API for this use case?
Claude Code is a pre-built interactive agent that runs in your terminal. It already knows how to execute bash commands, read files, and iterate on tasks — you just configure it and give it instructions.
The Claude API (specifically the tool use / function calling feature) is the underlying capability. You’d use the API directly if you want to build a fully custom application — for example, a web interface where executives log in, or a service that runs on a cloud server and handles multiple users. The API gives you more control and flexibility; Claude Code gives you a working agent out of the box with less setup.
For an individual building a personal assistant, Claude Code is the faster path. For a team or SaaS product serving multiple users, the Claude API is the right foundation.
How do you add Google Meet links to calendar events automatically?
The calendar_helper.py script already handles this — when you pass attendees to create_event(), the script sets conferenceDataVersion=1 and includes a createRequest in the conference data. Google generates a Meet link automatically and includes it in the event.
The hangoutLink field in the response contains the Meet URL. If you’re creating events without attendees (personal reminders, focus blocks), leave the attendees empty and no Meet link is created.
Key Takeaways
Building an AI executive assistant with Claude Code and Google Workspace APIs is a practical project that delivers real value quickly. Here’s what to take away:
- The core stack is three scripts — one each for Gmail, Calendar, and Docs — plus a
CLAUDE.mdthat tells Claude Code how to use them. Everything else builds on top of this. - OAuth setup is the biggest friction point. Once token.json is created and scopes are configured correctly, the actual API calls are straightforward.
- The real value is multi-step tasks, not single API calls. Inbox triage, post-meeting doc creation, and scheduling workflows that involve reading context and taking action are where an AI assistant earns its keep.
- Scheduled automation requires an external trigger for Claude Code — use cron on your local machine, or a platform like MindStudio if you want managed scheduling.
- Start with read-only permissions and add write access incrementally as you build trust in the assistant’s behavior. Marking emails as read and creating docs are safe to automate early; sending emails and creating calendar invites should have a confirmation step until you’re confident.
CLAUDE.mdis your leverage point. The more precisely you define the assistant’s behavior, preferences, and decision rules, the more useful and reliable it becomes.
If you want to skip the setup and get directly to the workflows, MindStudio gives you the same capabilities through a visual builder with Google Workspace integrations built in — no OAuth configuration, no Python scripts, no cron jobs. Try it free at mindstudio.ai.