1. Create a Google Cloud Project#
- Go to https://console.cloud.google.com/
- Click "Select a project" → "New Project"
- Name it "Aexy" and click "Create"
- Select the newly created project
2. Enable Required APIs#
- Go to APIs & Services → Library
- Enable the following APIs:
- Gmail API - For email sync
- Google Calendar API - For calendar sync
- Google People API - For contact info (optional)
3. Configure OAuth Consent Screen#
-
Go to APIs & Services → OAuth consent screen
-
Select External (or Internal for Workspace)
-
Fill in the app information:
- App name: "Aexy"
- User support email: your email
- Developer contact: your email
-
Click "Save and Continue"
-
Add scopes on the Scopes page:
Gmail Scopes:
https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.send https://www.googleapis.com/auth/gmail.modifyCalendar Scopes:
https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.eventsProfile Scopes:
https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile -
Add test users (required during development)
-
Click "Save and Continue"
4. Create OAuth Credentials#
- Go to APIs & Services → Credentials
- Click "Create Credentials" → "OAuth client ID"
- Application type: Web application
- Name: "Aexy Web Client"
- Add Authorized redirect URIs:
For production:# CRM Google Integration http://localhost:8000/api/v1/workspaces/{workspace_id}/integrations/google/callback http://localhost:8000/api/v1/auth/google/callback # Booking Calendar Integration http://localhost:8000/api/v1/booking/calendars/callback/googlehttps://your-domain.com/api/v1/workspaces/{workspace_id}/integrations/google/callback https://your-domain.com/api/v1/auth/google/callback https://your-domain.com/api/v1/booking/calendars/callback/google - Click "Create"
- Download the JSON credentials file
5. Get Your Credentials#
From the OAuth 2.0 Client ID you created:
- Client ID (ends with
.apps.googleusercontent.com) - Client Secret
6. Add to Backend .env#
# Add to backend/.env
GOOGLE_CLIENT_ID=your_client_id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_REDIRECT_URI=http://localhost:8000/api/v1/auth/google/callback
# For production
# GOOGLE_REDIRECT_URI=https://your-domain.com/api/v1/auth/google/callback
7. Restart Backend#
docker compose restart backend temporal-worker
8. Connect from CRM#
Option A: During Onboarding#
- Go through CRM onboarding flow
- At the "Connect" step, click "Connect with Google"
- Authorize Gmail and Calendar access
- Continue with onboarding
Option B: From Settings#
- Go to CRM → Settings → Integrations
- Click "Connect Google Account"
- Authorize Gmail and Calendar access
- Enable sync options
Token Management & Refresh#
How Tokens Work#
Google uses OAuth 2.0 with token expiration:
- Access Token: Used to make API calls (expires in 1 hour)
- Refresh Token: Used to get new access tokens (long-lived)
- ID Token: Contains user identity info (for sign-in)
Token Refresh#
The backend automatically refreshes tokens:
- Tokens are refreshed 5 minutes before expiry
- If refresh fails, user needs to re-authorize
- Refresh tokens can be revoked by Google if unused for 6 months
Updating Scopes (Re-authorization Required)#
If you need additional scopes:
- Add scopes in Google Cloud Console
- Users must re-authorize to grant new permissions
- Direct them to the connect URL:
/crm/settings/integrations
Checking Token Status#
# Test if token is valid
curl -X GET "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=YOUR_ACCESS_TOKEN"
Gmail Sync#
How Sync Works#
- Initial Full Sync: Fetches last N emails (configurable, default 1000)
- Incremental Sync: Uses Gmail History API to fetch only new/changed emails
- Periodic Sync: Temporal schedule
check-gmail-auto-syncpolls every 60 seconds for integrations due for a sync
Synced Data#
For each email, we store:
- Gmail ID and Thread ID
- Subject and snippet
- From/To addresses
- Body text (plain text version)
- Labels (Inbox, Sent, etc.)
- Date received
- Read status
Email to Record Linking#
Emails are automatically linked to CRM records by:
- Matching
from_emailto Person records - Matching email domain to Company records
- Manual linking via UI
Privacy Controls#
Users can configure:
- Which labels to sync (Inbox, Sent, All)
- Auto-create contacts from emails
- AI enrichment from signatures
Calendar Sync#
How Sync Works#
- Initial Sync: Fetches events from selected calendars
- Incremental Sync: Uses sync tokens to fetch only changes
- Periodic Sync: Shares the same
check-gmail-auto-syncTemporal schedule that drives mail sync
Synced Data#
For each event, we store:
- Google Event ID and Calendar ID
- Title and description
- Start/end time
- Location (including meeting links)
- Attendees list
- Organizer
- Status (confirmed, tentative, cancelled)
Event to Record Linking#
Events are linked to CRM records by:
- Matching attendee emails to Person records
- Manual linking via UI
- Creating CRM activities from meetings
Calendar Selection#
Users can choose which calendars to sync:
- Primary calendar (default)
- Additional calendars they have access to
- Shared team calendars
AI Contact Enrichment#
How It Works#
- Signature Extraction: AI parses email signatures for contact info
- Contact Classification: AI classifies contacts as lead, customer, vendor, etc.
- Company Detection: Extracts company info from email domain and signatures
Extracted Data#
From email signatures, we extract:
- Full name
- Job title
- Company name
- Phone numbers
- LinkedIn URL
- Physical address
Running Enrichment#
Enrichment runs automatically:
- After email sync
- Can be triggered manually from settings
# Manual trigger via API
curl -X POST http://localhost:8000/api/v1/workspaces/{workspace_id}/integrations/google/enrich \
-H "Authorization: Bearer YOUR_JWT"
API Endpoints#
Connection#
GET /workspaces/{workspace_id}/integrations/google/connect # Get OAuth URL
GET /workspaces/{workspace_id}/integrations/google/callback # OAuth callback
GET /workspaces/{workspace_id}/integrations/google/status # Connection status
PATCH /workspaces/{workspace_id}/integrations/google/settings # Update settings
POST /workspaces/{workspace_id}/integrations/google/disconnect # Disconnect
Gmail#
POST /workspaces/{workspace_id}/integrations/google/gmail/sync # Trigger sync
GET /workspaces/{workspace_id}/integrations/google/gmail/emails # List emails
GET /workspaces/{workspace_id}/integrations/google/gmail/emails/{id} # Get email
POST /workspaces/{workspace_id}/integrations/google/gmail/send # Send email
POST /workspaces/{workspace_id}/integrations/google/gmail/emails/{id}/link # Link to record
Calendar#
GET /workspaces/{workspace_id}/integrations/google/calendar/calendars # List calendars
POST /workspaces/{workspace_id}/integrations/google/calendar/sync # Trigger sync
GET /workspaces/{workspace_id}/integrations/google/calendar/events # List events
GET /workspaces/{workspace_id}/integrations/google/calendar/events/{id} # Get event
POST /workspaces/{workspace_id}/integrations/google/calendar/events # Create event
POST /workspaces/{workspace_id}/integrations/google/calendar/events/{id}/link # Link to record
Enrichment#
POST /workspaces/{workspace_id}/integrations/google/enrich # Run enrichment
POST /workspaces/{workspace_id}/integrations/google/records/{id}/enrich # Enrich record
Troubleshooting#
"access_denied" Error#
- User denied consent - they need to authorize again
- App not verified - add user as test user in console
- Scopes not approved - check consent screen configuration
"invalid_grant" Error#
Refresh token is invalid. Possible causes:
- User revoked access
- Token expired (6 months of inactivity)
- Too many refresh tokens (limit of 50 per user)
Solution: User must re-authorize the app.
"insufficient_permission" Error#
Token doesn't have required scopes:
- Check granted scopes in
google_integrationstable - User needs to re-authorize with additional scopes
Emails Not Syncing#
- Check Gmail sync is enabled in settings
- Verify token is valid
- Check the Temporal worker is running:
docker compose logs temporal-worker
Calendar Events Missing#
- Verify calendar sync is enabled
- Check which calendars are selected for sync
- Verify sync token is valid
Rate Limits#
Google API quotas:
- Gmail: 250 quota units/second (varies by operation)
- Calendar: 1,000,000 queries/day
If hitting limits:
- Implement exponential backoff
- Reduce sync frequency
- Use batch requests
Testing the Integration#
# Check connection status
curl http://localhost:8000/api/v1/workspaces/{workspace_id}/integrations/google/status \
-H "Authorization: Bearer YOUR_JWT"
# Trigger manual sync
curl -X POST http://localhost:8000/api/v1/workspaces/{workspace_id}/integrations/google/gmail/sync \
-H "Authorization: Bearer YOUR_JWT"
# List synced emails
curl http://localhost:8000/api/v1/workspaces/{workspace_id}/integrations/google/gmail/emails \
-H "Authorization: Bearer YOUR_JWT"
Database Tables#
google_integrations#
Stores OAuth tokens and sync settings per workspace.
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| workspace_id | UUID | FK to workspaces |
| google_email | VARCHAR | Connected Google account |
| access_token | TEXT | Encrypted access token |
| refresh_token | TEXT | Encrypted refresh token |
| token_expiry | TIMESTAMP | Token expiration time |
| granted_scopes | JSONB | List of authorized scopes |
| gmail_sync_enabled | BOOLEAN | Gmail sync toggle |
| calendar_sync_enabled | BOOLEAN | Calendar sync toggle |
| gmail_history_id | VARCHAR | For incremental Gmail sync |
| calendar_sync_token | TEXT | For incremental calendar sync |
synced_emails#
Stores synced Gmail messages.
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| workspace_id | UUID | FK to workspaces |
| gmail_id | VARCHAR | Gmail message ID |
| gmail_thread_id | VARCHAR | Gmail thread ID |
| subject | TEXT | Email subject |
| from_email | VARCHAR | Sender email |
| from_name | VARCHAR | Sender name |
| to_emails | JSONB | Recipients |
| snippet | TEXT | Email preview |
| body_text | TEXT | Plain text body |
| labels | JSONB | Gmail labels |
| is_read | BOOLEAN | Read status |
| gmail_date | TIMESTAMP | Original email date |
synced_calendar_events#
Stores synced Google Calendar events.
| Column | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| workspace_id | UUID | FK to workspaces |
| google_event_id | VARCHAR | Google event ID |
| google_calendar_id | VARCHAR | Calendar ID |
| title | TEXT | Event title |
| description | TEXT | Event description |
| location | TEXT | Location/meeting link |
| start_time | TIMESTAMP | Event start |
| end_time | TIMESTAMP | Event end |
| is_all_day | BOOLEAN | All-day flag |
| attendees | JSONB | List of attendees |
| organizer_email | VARCHAR | Organizer email |
| status | VARCHAR | confirmed/tentative/cancelled |
Frontend Components#
CRM Homepage (/crm)#
The CRM homepage displays:
- Google Integration Banner: Shows connection status, prompts to connect if not connected
- Quick Access Cards: Links to Inbox, Calendar, Activities, and Integrations
- Standard Objects: Companies, People, Deals
- Custom Objects: User-created object types
Inbox Page (/crm/inbox)#
Email inbox view for synced Gmail messages:
Features:
- Email list with unread indicators
- Email detail panel with full content
- Reply functionality (sends via Gmail API)
- Link email to CRM record action
- Search and filter emails
- Manual sync trigger
Components:
EmailListItem: Displays email preview in listEmailDetail: Full email view with actionsLinkToRecordModal: Modal to link email to Person/Company
Calendar Page (/crm/calendar)#
Calendar view for synced Google Calendar events:
Features:
- Month view with event cards
- Day view for detailed event list
- Event detail modal
- Navigation (today, prev/next month)
- View mode toggle (month/week/day)
- Manual sync trigger
Components:
MonthView: Calendar grid with eventsDayView: List of events for selected dayEventCard: Event display (compact and full)EventDetailModal: Full event details
Integration Settings (/crm/settings/integrations)#
Settings page for managing Google integration:
Features:
- Connection status display
- Gmail sync enable/disable and manual sync
- Calendar sync enable/disable and manual sync
- AI enrichment trigger
- Disconnect functionality
Onboarding Connect Step (/crm/onboarding/connect)#
Step in CRM onboarding flow for Google connection:
Features:
- Benefits explanation (email sync, calendar, AI enrichment)
- Privacy settings toggles
- "Connect with Google" OAuth button
- Skip option (connect later in settings)
React Hooks#
useGoogleIntegrationStatus(workspaceId)#
Returns Google integration connection status.
const { status, isLoading, error, refresh } = useGoogleIntegrationStatus(workspaceId);
// status: { is_connected, gmail_sync_enabled, calendar_sync_enabled, google_email, ... }
useGoogleIntegrationConnect(workspaceId)#
Handles OAuth connect/disconnect flow.
const { connect, disconnect, isConnecting, isDisconnecting } = useGoogleIntegrationConnect(workspaceId);
// connect(["gmail", "calendar"]) - redirects to Google OAuth
// disconnect() - removes integration
useGoogleEmails(workspaceId)#
Manages synced emails.
const { emails, isLoading, isSyncing, error, refresh, sync, getEmail, sendEmail, linkToRecord } = useGoogleEmails(workspaceId);
useGoogleCalendarEvents(workspaceId)#
Manages synced calendar events.
const { events, calendars, isLoading, isSyncing, error, refresh, sync, getEvent, createEvent, linkToRecord } = useGoogleCalendarEvents(workspaceId);
File Structure#
frontend/src/
├── app/crm/
│ ├── page.tsx # CRM homepage with Google banner
│ ├── inbox/
│ │ └── page.tsx # Email inbox view
│ ├── calendar/
│ │ └── page.tsx # Calendar view
│ ├── settings/
│ │ └── integrations/
│ │ └── page.tsx # Integration settings
│ └── onboarding/
│ └── connect/
│ └── page.tsx # Google connect onboarding step
├── hooks/
│ └── useGoogleIntegration.ts # Google integration hooks
└── lib/
└── api.ts # googleIntegrationApi methods
backend/src/aexy/
├── models/
│ └── google_integration.py # GoogleIntegration, SyncedEmail, SyncedCalendarEvent
├── services/
│ ├── google_auth_service.py # OAuth token management
│ ├── gmail_sync_service.py # Gmail sync logic
│ ├── calendar_sync_service.py # Calendar sync logic
│ └── contact_enrichment_service.py # AI contact extraction
├── api/
│ └── google_integration.py # API routes
├── schemas/
│ └── google_integration.py # Pydantic schemas
└── processing/
└── temporal/activities/
└── google_sync.py # Temporal activities for Gmail/Calendar sync