Skip to main content
The email_lite.py script synchronizes recent emails from Gmail to provide morning context. It’s designed to be lightweight and optional - only runs if configured.

Command Syntax

.venv/bin/python scripts/email_lite.py
This script has no command-line options. All configuration is done through config/email.yaml.

Configuration

Create config/email.yaml to enable email sync:
# Maximum number of messages to fetch
max_messages: 20

# Gmail label to fetch from (default: INBOX)
label: INBOX
Configuration options:
max_messages
integer
default:"20"
Maximum number of recent emails to fetch from Gmail.
label
string
default:"INBOX"
Gmail label to fetch messages from. Common values:
  • INBOX - Inbox messages
  • UNREAD - Unread messages only
  • STARRED - Starred messages
  • SENT - Sent messages
  • Custom label names

Usage Examples

View recent emails

.venv/bin/python scripts/email_lite.py
Expected Output:
Emails recientes: 15
  [Project Update] Weekly status report [email protected]
  Re: Meeting tomorrow - confirmed [email protected]
  [URGENT] Server maintenance tonight [email protected]
  New feature request [email protected]
  Invoice #2024-03-001 [email protected]
Output shows the first 5 emails with subject (truncated to 50 chars) and sender.

No configuration file

If config/email.yaml doesn’t exist:
.venv/bin/python scripts/email_lite.py
Expected Output:
Emails recientes: 0
The script exits gracefully without fetching emails.

Environment Variables

Email sync requires Google OAuth credentials:
GOOGLE_CLIENT_ID
string
required
Google OAuth client ID from Google Cloud Console.
GOOGLE_CLIENT_SECRET
string
required
Google OAuth client secret.
GOOGLE_REFRESH_TOKEN
string
required
OAuth refresh token with gmail.readonly scope. Generated by setup-oauth.

Email Metadata

The script fetches these fields for each email:
  • id: Gmail message ID
  • subject: Email subject line
  • from: Sender email and name
  • date: Date/time the email was sent
Email content (body) is not fetched - only metadata. This keeps the sync fast and privacy-focused.

Demo Mode

For demos or testing without real Gmail access:
  1. Create config/demo_gmail_fake.yaml (gitignored):
    # Enable fake email mode
    
  2. Create config/demo/emails_fake.json:
    [
      {
        "id": "msg001",
        "subject": "Welcome to the team!",
        "from": "[email protected]",
        "date": "Mon, 3 Mar 2026 09:00:00 +0000"
      },
      {
        "id": "msg002",
        "subject": "Project kickoff meeting",
        "from": "[email protected]",
        "date": "Mon, 3 Mar 2026 10:30:00 +0000"
      }
    ]
    
The script will return fake emails instead of calling Gmail API.
Demo mode is useful for presentations, screenshots, or development without API quotas.

How It Works

  1. Check configuration: If config/email.yaml doesn’t exist, exit early
  2. Check demo mode: If config/demo_gmail_fake.yaml exists, return fake data
  3. Load credentials: Get OAuth refresh token from .env
  4. Refresh access token: Exchange refresh token for active access token
  5. Fetch messages: Call Gmail API users.messages.list with label filter
  6. Fetch metadata: For each message ID, call users.messages.get with format=metadata
  7. Parse headers: Extract Subject, From, and Date from message headers
  8. Return results: List of email metadata objects

API Scope Required

gmail.readonly
scope
required
Read-only access to Gmail messages and settings.API endpoint: https://www.googleapis.com/auth/gmail.readonly
If you see authentication errors, ensure gmail.readonly scope was authorized during setup-oauth. Re-run with --regenerate if needed.

Exit Codes

  • 0: Success - emails fetched or skipped (no config)
  • Non-zero: Not explicitly used - errors are caught and return empty list

Error Handling

The script handles errors gracefully:
  • Missing credentials: Returns empty list ([])
  • API errors: Returns empty list
  • Network timeout: Returns empty list
  • Invalid token: Returns empty list
All errors are silent to avoid breaking the morning routine.
To debug email sync issues, check:
  1. GOOGLE_REFRESH_TOKEN is set in .env
  2. Token has gmail.readonly scope
  3. Gmail API is enabled in Google Cloud Console
  4. You have internet connectivity

Privacy Considerations

This script is designed with privacy in mind:
  • ✅ Only fetches metadata (no email body content)
  • ✅ Respects Gmail labels (can limit to specific folders)
  • ✅ Uses read-only scope (gmail.readonly)
  • ✅ Does not store emails locally (unless you extend it)
  • ✅ Optional - disabled by default (requires config/email.yaml)

Integration with Morning Routine

The email command is step 4 in the run-morning routine:
# Step 4: Email
.venv/bin/python scripts/email_lite.py
It provides context about overnight or early-morning emails so you can:
  • Identify urgent messages
  • Prepare for meetings mentioned in emails
  • Plan responses or follow-ups

Advanced Configuration

Fetch only unread emails

max_messages: 50
label: UNREAD

Fetch from multiple labels

The script currently supports one label. To fetch from multiple labels, run the script multiple times or modify fetch_recent_emails() to accept a list.

Custom label names

Gmail allows custom labels. Use the exact label name:
label: "Work/Urgent"
max_messages: 10
  • setup-oauth - Configure Gmail API access
  • run-morning - Full morning routine (includes email sync as step 4)
  • calendar - Related morning context from calendar events

Function Reference

If you’re extending the script, these functions are available:

get_email_config() -> dict

Reads config/email.yaml and returns configuration dictionary.

fetch_recent_emails(max_messages: int = 20, label: str = "INBOX") -> list

Fetches recent emails from Gmail API. Parameters:
  • max_messages: Maximum number of emails to fetch
  • label: Gmail label to filter by
Returns: List of dicts with id, subject, from, date

get_recent_emails_if_configured() -> list

Main entry point. Checks if config/email.yaml exists, handles demo mode, and fetches emails. Returns: List of email metadata or empty list

Troubleshooting

No emails returned

  1. Check config/email.yaml exists:
    cat config/email.yaml
    
  2. Verify credentials:
    grep GOOGLE_REFRESH_TOKEN resources/secrets/.env
    
  3. Test Gmail API access:
    .venv/bin/python -c "from scripts.email_lite import fetch_recent_emails; print(len(fetch_recent_emails()))"
    

Token expired or invalid

Re-run OAuth setup:
.venv/bin/python scripts/setup_oauth.py --regenerate
Enable demo mode to test the morning routine without real Gmail access during development or demos.

Build docs developers (and LLMs) love