Agentic Browser

Home Projects Agentic Browser Python Backend Api Api Routers Gmail And Calendar Integration

Gmail and Calendar Integration

This document describes the Gmail and Calendar integration routers that expose Google Workspace API functionality through the FastAPI backend. These routers provide endpoints for email management (reading, sending, marking as read) and calendar operations (listing events, creating events). Both routers require Google OAuth2 access tokens obtained through the browser extension's authentication flow.

For information about the authentication mechanism that provides these access tokens, see Settings and Authentication. For details on how the agent system dynamically loads these tools based on available credentials, see Tool System.

Architecture Overview

The Gmail and Calendar routers are mounted on the main FastAPI application and provide REST endpoints that wrap Google API calls. These routers accept OAuth2 access tokens in request bodies and forward authenticated requests to Google's Gmail and Calendar APIs.

Sources: api/main.py33-34 routers/gmail.py1-141 routers/calendar.py1-106

Gmail Router

The Gmail router is mounted at /api/gmail and provides four endpoints for email operations. All endpoints accept a TokenRequest or derived model containing the Google OAuth2 access token.

Endpoints and Request Models

Architecture Diagram

Sources: routers/gmail.py15-35 routers/gmail.py37-141

Endpoint Request Model Tool Function Description
/unread UnreadRequest list_unread() Lists unread emails with configurable max results
/latest LatestRequest get_latest_emails() Fetches most recent emails regardless of read status
/mark_read MarkReadRequest mark_read() Marks a specific message as read by message ID
/send SendEmailRequest send_email() Sends an email to specified recipient

Sources: routers/gmail.py37-141

Request Model Hierarchy

All Gmail request models inherit from TokenRequest, which provides the base access_token field required for Google API authentication:

Sources: routers/gmail.py15-35

Implementation Details

Each endpoint follows a consistent pattern:

  1. Validates required fields (raises HTTPException with status 400 if missing)
  2. Sets default values for optional parameters
  3. Calls the corresponding tool function from tools/gmail/
  4. Returns formatted response with consistent structure
  5. Catches exceptions and logs errors before raising HTTPException with status 500

List Unread Endpoint:

# routers/gmail.py:37-60
@router.post("/unread", response_model=dict)
async def list_unread_messages(request: UnreadRequest)

The endpoint validates access_token, ensures max_results is positive (defaulting to 10), then calls list_unread(). Response format: {"messages": [...]}

Latest Emails Endpoint:

# routers/gmail.py:63-88
@router.post("/latest", response_model=dict)
async def fetch_latest(request: LatestRequest)

Similar validation pattern with default max_results=5, calls get_latest_emails(). Response format: {"messages": [...]}

Mark Read Endpoint:

# routers/gmail.py:91-113
@router.post("/mark_read", response_model=dict)
async def mark_message_read(request: MarkReadRequest)

Requires both access_token and message_id, calls mark_read(). Response format: {"result": "ok", "details": {...}}

Send Email Endpoint:

# routers/gmail.py:116-140
@router.post("/send", response_model=dict)
async def send_message(request: SendEmailRequest)

Validates access_token, to, and subject fields, calls send_email(). Response format: {"result": "sent", "details": {...}}

Sources: routers/gmail.py1-141

Tool Function Integration

The router imports tool functions that directly interact with Gmail API:

# routers/gmail.py:6-9
from tools.gmail.list_unread_emails import list_unread
from tools.gmail.fetch_latest_mails import get_latest_emails
from tools.gmail.mark_email_read import mark_read
from tools.gmail.send_email import send_email

Each tool function accepts the OAuth access token and makes authenticated HTTPS requests to Gmail API endpoints using the requests library with Authorization: Bearer {access_token} headers.

Sources: routers/gmail.py6-9

Calendar Router

The Calendar router is mounted at /api/calendar and provides two endpoints for calendar operations. Like the Gmail router, it uses Pydantic models that inherit from a base TokenRequest.

Endpoints and Request Models

Sources: routers/calendar.py14-27 routers/calendar.py29-105

Endpoint Request Model Tool Function Description
/events EventsRequest get_calendar_events() Lists upcoming calendar events
/create CreateEventRequest create_calendar_event() Creates a new calendar event

Sources: routers/calendar.py29-105

Request Model Structures

Sources: routers/calendar.py14-27

Events List Endpoint

The /events endpoint retrieves upcoming calendar events from the user's primary calendar:

# routers/calendar.py:29-51
@router.post("/events", response_model=dict)
async def list_events(request: EventsRequest)

Validation:

  • Checks access_token is provided
  • Ensures max_results is positive (defaults to 10)

Processing:

  • Calls get_calendar_events(access_token, max_results)
  • Returns response: {"events": [...]}

The underlying tool function at tools/calendar/get_calender_events.py6-23 makes a GET request to https://www.googleapis.com/calendar/v3/calendars/primary/events with query parameters:

  • maxResults: Number of events to return
  • orderBy: "startTime" for chronological ordering
  • singleEvents: True to expand recurring events
  • timeMin: Current UTC time to only fetch future events

Sources: routers/calendar.py29-51 tools/calendar/get_calender_events.py6-23

Event Creation Endpoint

The /create endpoint creates new calendar events with ISO 8601 formatted timestamps:

# routers/calendar.py:64-105
@router.post("/create", response_model=dict)
async def create_event(request: CreateEventRequest)

Validation:

  1. Required fields: access_token, summary, start_time, end_time
  2. ISO 8601 format validation using helper function _is_isoformat()

The helper validates ISO format:

# routers/calendar.py:54-62
def _is_isoformat(s: str) -> bool:
    try:
        datetime.fromisoformat(s)
        return True
    except Exception:
        return False

Processing:

  • Calls create_calendar_event() with validated parameters
  • Returns response: {"result": "created", "event": {...}}

The tool function at tools/calendar/create_calender_events.py6-40 constructs event data with:

  • summary: Event title
  • description: Event description
  • start: Dictionary with dateTime (ISO format) and timeZone ("UTC")
  • end: Dictionary with dateTime (ISO format) and timeZone ("UTC")

Makes POST request to https://www.googleapis.com/calendar/v3/calendars/primary/events.

Sources: routers/calendar.py54-105 tools/calendar/create_calender_events.py6-40

Authentication Token Flow

Both routers rely on OAuth2 access tokens obtained through the browser extension's authentication system. The tokens are passed in request bodies rather than headers to simplify the API contract.

Sources: routers/gmail.py37-140 routers/calendar.py29-105

Token Handling Pattern

All endpoints follow this token handling pattern:

  1. Receive token in request body: Token included as part of Pydantic request model
  2. No token storage: Routers do not cache or store tokens
  3. Stateless operation: Each request must include a valid token
  4. Bearer authentication: Tools format token as Authorization: Bearer {access_token} header
  5. Error propagation: Invalid/expired tokens cause Google API to return 401, which raises exception

Sources: routers/gmail.py1-141 routers/calendar.py1-106

Integration with Agent System

These routers are also exposed as agent tools through the LangChain-based agent system. The agent dynamically loads Gmail and Calendar tools only when google_access_token is present in the execution context.

Dynamic Tool Loading

The agent's tool builder conditionally includes Google tools:

# Referenced from agent system (see section 4.2)
if context.google_access_token:
    tools.extend([
        gmail_agent,
        gmail_send_email,
        gmail_list_unread,
        gmail_mark_read,
        calendar_agent,
        calendar_create_event
    ])

Tool Invocation Pattern

When the agent decides to invoke a Gmail or Calendar tool, it:

  1. Extracts google_access_token from context
  2. Constructs tool-specific parameters (e.g., max_results, message_id)
  3. Makes internal API call to the appropriate router endpoint
  4. Processes response and returns formatted output to LLM

This architecture allows the same Gmail/Calendar functionality to be used both:

  • Directly: Via REST API calls from the browser extension
  • Indirectly: Through the agent system's tool invocation mechanism

Sources: api/main.py33-34 routers/__init__.py10-12

Error Handling and Logging

Both routers implement consistent error handling:

# Pattern used in all endpoints
try:
    # Validate request
    # Call tool function
    # Return formatted response
except HTTPException:
    raise  # Re-raise validation errors
except Exception as e:
    logger.exception("Error message: %s", e)
    raise HTTPException(status_code=500, detail=str(e))

This pattern:

  • Preserves validation errors: 400-level HTTPExceptions pass through unchanged
  • Logs unexpected errors: All other exceptions logged with full traceback
  • Returns clean errors: Converts exceptions to 500 HTTPException with error message
  • Uses structured logging: Logger obtained via get_logger(__name__) from core.config

Sources: routers/gmail.py52-60 routers/calendar.py43-51

Router Registration

Both routers are registered on the main FastAPI application with prefixes:

# api/main.py:33-34
app.include_router(gmail_router, prefix="/api/gmail")
app.include_router(calendar_router, prefix="/api/calendar")

The routers are imported via the routers package:

# api/main.py:22-23
from routers import gmail_router, calendar_router

# routers/__init__.py:10-11
from .gmail import router as gmail_router
from .calendar import router as calendar_router

Sources: api/main.py16-34 routers/__init__.py1-25