-
Notifications
You must be signed in to change notification settings - Fork 109
Examples Email Agent Example
aaron edited this page Nov 30, 2025
·
2 revisions
A complete, working example of an AI agent that can search for emails and send messages. This example demonstrates tool creation, error handling, and real-world API integration.
The Email Agent can:
- Search your Gmail inbox by query
- Send emails to specific recipients
- Handle multiple requests in conversation
- Provide clear error messages
Technologies used:
- ConnectOnion for the agent framework
- Gmail API for email operations
- OAuth 2.0 for authentication
from connectonion import Agent
from connectonion.decorators import xray
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
import base64
from email.mime.text import MIMEText
import os.path
# Gmail API scopes
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly',
'https://www.googleapis.com/auth/gmail.send']
def get_gmail_service():
"""Authenticate and return Gmail API service"""
creds = None
# Load saved credentials
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If no valid credentials, let user log in
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)
# Save credentials
with open('token.json', 'w') as token:
token.write(creds.to_json())
return build('gmail', 'v1', credentials=creds)
@xray # Debug this tool if needed
def search_emails(query: str, max_results: int = 5) -> str:
"""
Search Gmail inbox for emails matching a query.
Args:
query: Search query (e.g., "from:john", "subject:meeting", "after:2024/01/01")
max_results: Maximum number of results to return (1-10)
Returns:
List of matching emails with sender, subject, and snippet
"""
try:
service = get_gmail_service()
# Search emails
results = service.users().messages().list(
userId='me',
q=query,
maxResults=min(max_results, 10)
).execute()
messages = results.get('messages', [])
if not messages:
return f"No emails found matching '{query}'"
# Get details for each email
email_list = []
for msg in messages:
msg_data = service.users().messages().get(
userId='me',
id=msg['id'],
format='full'
).execute()
# Extract headers
headers = msg_data['payload']['headers']
subject = next((h['value'] for h in headers if h['name'] == 'Subject'), 'No Subject')
sender = next((h['value'] for h in headers if h['name'] == 'From'), 'Unknown')
date = next((h['value'] for h in headers if h['name'] == 'Date'), 'Unknown')
# Get snippet
snippet = msg_data.get('snippet', '')
email_list.append(f"""
Email #{len(email_list) + 1}:
From: {sender}
Date: {date}
Subject: {subject}
Preview: {snippet[:100]}...
""")
result = f"Found {len(email_list)} emails:\n" + "\n".join(email_list)
return result
except HttpError as error:
return f"Gmail API error: {error}"
except Exception as e:
return f"Error searching emails: {str(e)}"
@xray # Debug this tool if needed
def send_email(to: str, subject: str, body: str) -> str:
"""
Send an email via Gmail.
Args:
to: Recipient email address
subject: Email subject line
body: Email message content
Returns:
Success confirmation or error message
"""
try:
# Validate inputs
if "@" not in to:
return f"Error: '{to}' is not a valid email address"
if not subject or len(subject) < 2:
return "Error: Email must have a subject line"
if not body or len(body) < 10:
return "Error: Email body too short (minimum 10 characters)"
# Create message
message = MIMEText(body)
message['to'] = to
message['subject'] = subject
# Encode message
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
# Send via Gmail API
service = get_gmail_service()
sent_message = service.users().messages().send(
userId='me',
body={'raw': raw_message}
).execute()
return f"✓ Email sent successfully to {to} (Message ID: {sent_message['id']})"
except HttpError as error:
return f"Gmail API error: {error}"
except Exception as e:
return f"Error sending email: {str(e)}"
# Create the Email Agent
email_agent = Agent(
name="email-assistant",
tools=[search_emails, send_email],
system_prompt="""You are an email assistant that helps manage Gmail.
Your capabilities:
- Search emails using Gmail search syntax (from:, to:, subject:, after:, before:, etc.)
- Send emails to specific recipients
Best practices:
- When searching, use specific queries to find relevant emails
- Before sending, confirm recipient and content
- Provide clear confirmations for actions taken
- If unsure about email addresses, search for them first
Always be helpful and accurate with email operations.""",
model="gpt-4o-mini" # Fast and cost-effective
)
# Example usage
if __name__ == "__main__":
# Enable debugging (optional)
# email_agent.auto_debug()
# Example 1: Search for emails
print("=== Example 1: Search emails ===")
email_agent.input("Find emails from john in the last week")
# Example 2: Send an email
print("\n=== Example 2: Send email ===")
email_agent.input("Send an email to [email protected] with subject 'Meeting Reminder' and body 'Don't forget our meeting tomorrow at 2 PM'")
# Example 3: Multi-step task
print("\n=== Example 3: Search and reply ===")
email_agent.input("Search for emails about 'project update' and tell me who sent them")pip install connectonion google-auth-oauthlib google-auth-httplib2 google-api-python-clienta. Enable Gmail API:
- Go to Google Cloud Console
- Create a new project or select existing
- Enable Gmail API for the project
- Create OAuth 2.0 credentials (Desktop app)
- Download credentials as
credentials.json
b. Place credentials:
# Put credentials.json in your project directory
mv ~/Downloads/credentials.json ./credentials.jsonpython email_agent.pyOn first run:
- Browser opens for Google sign-in
- Grant permissions to your app
-
token.jsonis created (stores credentials) - Agent is ready to use!
# Create .env file
echo "OPENAI_API_KEY=sk-your-key-here" > .envemail_agent.input("Find emails from Sarah")Output:
Found 3 emails:
Email #1:
From: Sarah Johnson <[email protected]>
Date: Mon, 15 Jan 2024 14:30:00
Subject: Project Update
Preview: Hi team, I wanted to share our progress...
Email #2:
From: Sarah Johnson <[email protected]>
Date: Wed, 10 Jan 2024 09:15:00
Subject: Meeting Notes
Preview: Thanks for attending today's meeting...
Email #3:
From: Sarah Johnson <[email protected]>
Date: Fri, 05 Jan 2024 16:45:00
Subject: Quick Question
Preview: Do you have a moment to discuss...
# Gmail search syntax
email_agent.input("Find unread emails with attachments from last month")
# Equivalent Gmail query: is:unread has:attachment after:2024/01/01email_agent.input("Send an email to [email protected] thanking him for the project update")Output:
✓ Email sent successfully to [email protected] (Message ID: abc123...)
# Turn 1
email_agent.input("Search for emails about the quarterly review")
# Turn 2 (agent remembers context)
email_agent.input("Who sent the most recent one?")
# Turn 3
email_agent.input("Send them a reply saying I'll have the report ready by Friday")Enable interactive debugging to see what's happening:
email_agent.auto_debug()
email_agent.input("Find emails from John and send him a reply")
# When agent pauses at breakpoint:
# > Press 'c' to continue
# > Press 'a' to ask AI why it made certain choices
# > Press 'e' to modify search results or email content
# > Press 'v' to see execution tracedef mark_as_read(email_ids: str) -> str:
"""Mark emails as read"""
# Implementation
pass
def archive_emails(query: str) -> str:
"""Archive emails matching query"""
# Implementation
pass
def get_labels() -> str:
"""List all Gmail labels"""
# Implementation
passdef search_emails(
query: str,
max_results: int = 5,
sort_by: str = "date" # date, from, subject
) -> str:
"""Search with sorting options"""
# Implementation
passdef send_email_with_attachment(
to: str,
subject: str,
body: str,
attachment_path: str
) -> str:
"""Send email with file attachment"""
# Implementation
passSolution: Download OAuth credentials from Google Cloud Console
Solution:
- Delete
token.json - Re-run the script
- Grant all requested permissions
Solution: Gmail has sending limits:
- New accounts: ~100-500 emails/day
- Established accounts: ~2000 emails/day
# Tools return error messages as strings
# Agent sees them and can adapt
if error:
return f"Error: {error_message}"
else:
return f"Success: {result}"# Validate before API calls
if "@" not in to:
return "Error: Invalid email address"# Bad - unclear
return "Done"
# Good - specific
return f"✓ Email sent to {to}"@xray # Debug email sending
def send_email(...):
pass
# Don't need @xray for simple searches
def search_emails(...):
passEnhance This Example:
- Add calendar integration
- Implement email templates
- Add bulk operations
- Create email summaries
Learn More:
More Examples:
Complete code: Download email_agent.py