Skip to main content

Overview

The Email OAuth integration allows users to connect their Gmail and Outlook accounts to the email agent. This document explains how the OAuth flow works and what’s been implemented.

OAuth Flow Architecture

┌─────────────┐    1. Click "Connect Gmail/Outlook"    ┌──────────────────┐
│   Frontend  │ ─────────────────────────────────────> │  /oauth/init     │
│   Settings  │                                         │    (Backend)     │
│    Page     │                                         └──────────────────┘
└─────────────┘                                                  │
       ↑                                                         │ 2. Generate
       │                                                         │    OAuth URL
       │                                                         ↓
       │                                            ┌──────────────────┐
       │                                            │    Composio      │
       │                                            │     OAuth        │
       │                                            │   (External)     │
       │                                            └──────────────────┘
       │                                                         │
       │                                                         │ 3. User grants
       │                                                         │    permission
       │                                                         ↓
       │                                            ┌──────────────────┐
       │                                            │  /oauth/callback │
       │  5. Redirect with                          │    (Backend)     │
       │     success/error                          └──────────────────┘
       │    <─────────────────────────────────────          │
       │                                                         │ 4. Store tokens
       │                                                         │    in Supabase
       └─────────────┘                                          ↓
         6. Show toast                              ┌──────────────────┐
         7. Refresh list                            │    Supabase      │
                                                   │     Tables       │
                                                   └──────────────────┘

Backend Implementation

Gmail OAuth Endpoints

POST /email_bot/gmail/oauth/init

Initializes the Gmail OAuth flow. Request:
{
  "origin_url": "http://localhost:3000/email-agent-settings"
}
Response:
{
  "auth_url": "https://composio.dev/...",
  "state": "base64_encoded_state"
}
What it does:
  1. Creates a secure state token with user context
  2. Stores state in memory for validation
  3. Builds Composio authorization URL
  4. Returns URL for frontend to redirect to

GET /email_bot/gmail/oauth/callback

Handles the OAuth callback from Composio. Query Parameters:
  • state: The state token from init
  • connected_account_id: Composio account ID
  • status: OAuth status (success/error)
  • error: Error message (if any)
What it does:
  1. Validates state token
  2. Checks state expiration (15 min timeout)
  3. Gets Gmail email address from Composio using GMAIL_GET_PROFILE tool
  4. Stores account in email_oauth_tokens table
  5. Updates email_config with connected email
  6. Redirects back to frontend with result
Redirect URLs:
  • Success: {origin_url}?oauth=success&email={email}
  • Error: {origin_url}?oauth=error&message={error}

Outlook OAuth Endpoints

POST /email_bot/outlook/oauth/init

Initializes the Outlook OAuth flow (same pattern as Gmail).

GET /email_bot/outlook/oauth/callback

Handles OAuth callback for Outlook using OUTLOOK_OUTLOOK_GET_PROFILE tool.

Database Schema

email_oauth_tokens Table

Stores OAuth credentials for connected accounts.
CREATE TABLE email_oauth_tokens (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  user_id UUID NOT NULL REFERENCES users(id),
  provider TEXT NOT NULL CHECK (provider IN ('google', 'microsoft')),
  email TEXT NOT NULL,
  composio_account_id TEXT NOT NULL,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  UNIQUE(provider, email)
);
Fields:
  • user_id: Reference to the user who connected the account
  • provider: 'google' for Gmail, 'microsoft' for Outlook
  • email: The connected email address
  • composio_account_id: Composio’s account identifier
  • Unique constraint ensures one account per email per provider

email_config Table

Stores user email configuration and preferences.
CREATE TABLE email_config (
  user_id UUID PRIMARY KEY REFERENCES users(id),
  connected_emails JSONB DEFAULT '[]'::jsonb,
  mode TEXT CHECK (mode IN ('draft', 'manual', 'auto')),
  contact_email TEXT,
  scheduling_email TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
connected_emails Format:
[
  {
    "email": "user@gmail.com",
    "provider": "google"
  },
  {
    "email": "user@outlook.com",
    "provider": "microsoft"
  }
]

Frontend Implementation

OAuth Integration

The frontend automatically handles both Gmail and Outlook OAuth callbacks. Location: /email-agent-settings page

Connect Gmail Button

const handleConnectGmail = async () => {
  setIsConnectingGmail(true)

  const response = await fetch(`${API_BASE_URL}/email_bot/gmail/oauth/init`, {
    method: "POST",
    credentials: "include",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      origin_url: window.location.origin + window.location.pathname
    })
  })

  const data = await response.json()
  window.location.href = data.auth_url
}

Connect Outlook Button

const handleConnectOutlook = async () => {
  setIsConnectingOutlook(true)

  const response = await fetch(`${API_BASE_URL}/email_bot/outlook/oauth/init`, {
    method: "POST",
    credentials: "include",
    headers: {
      "Authorization": `Bearer ${token}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      origin_url: window.location.origin + window.location.pathname
    })
  })

  const data = await response.json()
  window.location.href = data.auth_url
}

OAuth Callback Handler

Runs on component mount to detect OAuth redirects:
const handleOAuthCallback = () => {
  const params = new URLSearchParams(window.location.search)
  const oauthStatus = params.get("oauth")
  const email = params.get("email")
  const message = params.get("message")

  if (oauthStatus === "success" && email) {
    toast.success("Email connected successfully", {
      description: `${email} has been connected to your account`
    })

    // Clean up URL
    window.history.replaceState({}, "", window.location.pathname)

    // Refresh config to show new email
    fetchEmailConfig()
  } else if (oauthStatus === "error") {
    toast.error("Failed to connect email", {
      description: message || "An error occurred during authentication"
    })

    // Clean up URL
    window.history.replaceState({}, "", window.location.pathname)
  }
}

useEffect(() => {
  handleOAuthCallback()
}, [])

Connected Emails Display

Shows all connected Gmail and Outlook accounts with provider logos:
{emailConfig?.connected_emails.map((emailData, index) => (
  <div key={index} className="flex items-center justify-between p-4 border rounded-lg">
    <div className="flex items-center gap-3">
      {emailData.provider === 'google' ? (
        <img src="/gmail_new_logo_icon_159149.png" alt="Gmail" className="h-5 w-5" />
      ) : emailData.provider === 'microsoft' ? (
        <img src="/png-transparent-outlook-logo.png" alt="Outlook" className="h-5 w-5" />
      ) : (
        <Mail className="h-5 w-5" />
      )}
      <span>{emailData.email}</span>
    </div>
    <Badge>Connected</Badge>
  </div>
))}

Security Features

State Token Security

  • Generated with user context: Includes user ID and timestamp
  • Base64 encoded: URL-safe transmission
  • Server-side storage: Validated against stored states
  • 15-minute expiration: Prevents replay attacks
  • Single-use: Deleted after callback processing
# Backend state generation
state_data = {
    "user_id": user_id,
    "timestamp": datetime.now().isoformat(),
    "nonce": secrets.token_urlsafe(16)
}
state = base64.urlsafe_b64encode(json.dumps(state_data).encode()).decode()

Authorization

  • JWT authentication: All endpoints require valid JWT token
  • User isolation: Can only access own OAuth flows
  • Token extraction: User ID extracted from JWT
  • Firm-level access: RLS policies ensure data isolation

Composio Integration

  • Entity-based auth: User ID as Composio entity
  • Account ID storage: Store Composio IDs, not raw tokens
  • Automatic refresh: Composio handles token refresh
  • Secure callbacks: Validate all callback parameters

Environment Variables

Backend (.env)

# Supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_KEY=your-service-role-key
SUPABASE_JWT_SECRET=your-jwt-secret

# Composio
COMPOSIO_API_KEY=your-composio-api-key
COMPOSIO_GMAIL_AUTH_CONFIG_ID=your-gmail-config-id
COMPOSIO_OUTLOOK_AUTH_CONFIG_ID=your-outlook-config-id

# API Base URL (for callbacks)
API_BASE_URL=http://localhost:8000

Frontend (.env.local)

# Backend API
NEXT_PUBLIC_API_URL=http://localhost:8000

# Supabase (Frontend - use anon key)
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
Security:
  • Never use service role key in frontend
  • Use NEXT_PUBLIC_ prefix only for safe, client-side variables
  • Store sensitive keys only in backend .env

Testing the OAuth Flow

Manual Testing Steps

1

Start Backend Server

cd zarna-backend/api
python run.py
Verify: http://localhost:8000/health returns {"status": "healthy"}
2

Start Frontend Server

cd zarna-frontend
npm run dev
Verify: http://localhost:3000 loads successfully
3

Navigate to Settings

Go to http://localhost:3000/email-agent-settings and log in
4

Click Connect Gmail/Outlook

Button should show loading state, then redirect to OAuth provider
5

Grant Permissions

  • Select your Gmail or Outlook account
  • Grant required permissions
  • Click “Allow” or “Accept”
6

Verify Redirect

  • Should redirect back to settings page
  • Success toast should appear
  • Email should appear in connected accounts list
  • URL should be clean (no query parameters)

Expected Behavior

Success Flow:
  1. Click “Connect Gmail/Outlook” → Loading spinner appears
  2. Redirect to Google/Microsoft OAuth → Grant permissions
  3. Redirect back to settings → Green success toast
  4. Email appears in “Connected Email Accounts”
  5. URL is clean (no ?oauth=success&email=...)
  6. Provider logo displays correctly

Troubleshooting

Cause: Backend endpoint not responding or unauthorizedSolution:
  • Verify backend is running on localhost:8000
  • Check JWT token is valid
  • Ensure COMPOSIO_API_KEY is set in backend .env
  • Check browser console for error messages
Cause: State token expired (> 15 minutes) or tampered withSolution:
  • Try connecting again (state expires after 15 minutes)
  • Check system clock is synchronized
  • Ensure cookies/localStorage aren’t blocked
Cause: Composio API error or insufficient permissionsSolution:
  • Verify Composio API key is valid
  • Check COMPOSIO_GMAIL_AUTH_CONFIG_ID or COMPOSIO_OUTLOOK_AUTH_CONFIG_ID
  • Ensure OAuth app has correct scopes configured in Composio
  • Check backend logs for detailed error messages
Cause: Database update failed or frontend not refreshingSolution:
  • Check Supabase connection in backend
  • Verify table permissions allow INSERT/UPDATE
  • Check browser console for API errors
  • Try refreshing the page manually
Cause: Backend CORS not configured for frontend originSolution:
# In backend main.py, ensure CORS allows frontend:
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "http://localhost:3001"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

Gmail vs Outlook Comparison

FeatureGmailOutlook
Init Endpoint/email_bot/gmail/oauth/init/email_bot/outlook/oauth/init
Callback Endpoint/email_bot/gmail/oauth/callback/email_bot/outlook/oauth/callback
Provider Namegooglemicrosoft
Auth Config EnvCOMPOSIO_GMAIL_AUTH_CONFIG_IDCOMPOSIO_OUTLOOK_AUTH_CONFIG_ID
Profile ToolGMAIL_GET_PROFILEOUTLOOK_OUTLOOK_GET_PROFILE
Email FieldemailAddressmail
Logo/gmail_new_logo_icon_159149.png/png-transparent-outlook-logo.png
OAuth CallbackSame handlerSame handler

What’s Next

Once OAuth is working, you can:
  1. Disconnect emails: Add functionality to remove connected accounts
  2. Token refresh monitoring: Track and alert on token expiration
  3. Connection health checks: Verify tokens are still valid
  4. Send emails: Use Composio’s email sending capabilities
  5. Email syncing: Fetch emails and sync to database
  6. Email automation: Build automated email workflows

Summary

What’s Working:
  • Gmail OAuth initialization and callback
  • Outlook OAuth initialization and callback
  • Secure state management (15-min expiration)
  • Database persistence in Supabase
  • Frontend toast notifications
  • URL cleanup after redirect
  • Connected emails display with provider logos
  • Comprehensive error handling
🎯 Production Ready: The OAuth flow is fully functional and ready for production use. Just ensure proper environment variables are configured in your deployment environment.

Resources