Skip to main content

Overview

The Gmail integration can work in two modes:
  • Tool-only mode: Agent has Gmail tools (read, send, search, draft) available when triggered from other channels
  • Channel mode: Incoming emails can trigger the agent and receive automatic replies
Both modes use Google Cloud OAuth for authentication.

Installation

Run the /add-gmail skill in Claude Code:
/add-gmail
The skill will ask whether you want channel mode or tool-only mode, then guide you through setup.

Google Cloud Setup

1

Create GCP Project

  1. Go to console.cloud.google.com
  2. Create a new project or select an existing one
2

Enable Gmail API

  1. Go to APIs & ServicesLibrary
  2. Search for “Gmail API”
  3. Click Enable
3

Configure Consent Screen

  1. Go to APIs & ServicesOAuth consent screen
  2. Choose External user type
  3. Fill in:
    • App name (e.g., “NanoClaw Gmail”)
    • User support email (your email)
    • Developer contact email (your email)
  4. Click Save and Continue
  5. Skip adding scopes (the library handles this)
  6. Add yourself as a test user
  7. Click Save and Continue
4

Create OAuth Credentials

  1. Go to APIs & ServicesCredentials
  2. Click + CREATE CREDENTIALSOAuth client ID
  3. Application type: Desktop app
  4. Name: anything (e.g., “NanoClaw Gmail Client”)
  5. Click Create
  6. Click DOWNLOAD JSON and save as gcp-oauth.keys.json

Configuration

Install OAuth Credentials

Copy the downloaded credentials to ~/.gmail-mcp/:
mkdir -p ~/.gmail-mcp
cp /path/to/downloaded/gcp-oauth.keys.json ~/.gmail-mcp/gcp-oauth.keys.json

Authorize Access

Run the authorization flow:
npx -y @gongrzhe/server-gmail-autoauth-mcp auth
This will:
  1. Open your browser
  2. Ask you to sign in to Google
  3. Show an “app isn’t verified” warning (this is normal for personal OAuth apps)
  4. Ask for Gmail permissions
  5. Save credentials to ~/.gmail-mcp/credentials.json
If you see “This app isn’t verified” warning, click AdvancedGo to [app name] (unsafe). This is normal for personal OAuth apps that haven’t gone through Google’s verification process.

Rebuild Container

The Gmail integration requires rebuilding the agent container (for MCP server changes):
# Clear stale agent-runner copies
rm -r data/sessions/*/agent-runner-src 2>/dev/null || true

# Rebuild container
cd container && ./build.sh && cd ..

# Build and restart
npm run build
launchctl kickstart -k gui/$(id -u)/com.nanoclaw  # macOS
systemctl --user restart nanoclaw  # Linux

Tool-Only Mode

In tool-only mode, the agent has these Gmail tools available:
ToolPurpose
mcp__gmail__get_profileGet Gmail account info
mcp__gmail__list_labelsList all Gmail labels
mcp__gmail__search_emailsSearch emails with Gmail query syntax
mcp__gmail__get_emailGet full email content by ID
mcp__gmail__send_emailSend a new email
mcp__gmail__create_draftCreate a draft email

Example Usage

From any registered channel:
User: @Andy check my recent emails

Claude: [calls mcp__gmail__search_emails with query "is:unread newer_than:1d"]
        You have 3 unread emails from today:
        1. John Smith - Project update
        2. GitHub - PR review requested
        3. Calendar - Meeting reminder

User: @Andy reply to John and say I'll review by EOD

Claude: [calls mcp__gmail__send_email]
        Sent! John will receive your reply.

Channel Mode

In channel mode, the Gmail integration polls your inbox and triggers the agent when new emails arrive.

How It Works

  1. Polling: Checks for unread emails every 60 seconds (configurable)
  2. Filtering: By default, only emails in Primary inbox trigger the agent (Promotions, Social, Updates, Forums excluded)
  3. Processing: Agent receives email content and can use Gmail tools to reply
  4. Notification: Agent is notified in the main channel about new emails

Email Filtering

Default filter query:
is:unread category:primary
This means:
  • Only unread emails
  • Only emails in the Primary inbox category
  • Excludes Promotions, Social, Updates, and Forums
You can customize the filter query by modifying the searchQuery parameter in the GmailChannel constructor in src/channels/gmail.ts.

Thread Tracking

The channel tracks email threads to enable proper reply handling:
interface ThreadMeta {
  sender: string;           // Email address
  senderName: string;       // Display name
  subject: string;          // Email subject
  messageId: string;        // RFC 2822 Message-ID for In-Reply-To
}
When the agent sends a reply, it includes proper email headers for threading.

Email Notification Format

When a new email arrives, the agent receives:
[Email from John Smith <[email protected]>]
Subject: Project Update

Hi Andy,

Just wanted to give you a quick update on the project...
By default, the agent is instructed NOT to reply to emails automatically. Add instructions to groups/main/CLAUDE.md if you want different behavior.

Troubleshooting

OAuth Token Expired

Tokens expire after a period of inactivity. Re-authorize:
rm ~/.gmail-mcp/credentials.json
npx -y @gongrzhe/server-gmail-autoauth-mcp auth

Gmail Connection Not Responding

Test the connection directly:
npx -y @gongrzhe/server-gmail-autoauth-mcp
This starts the MCP server in stdio mode. You should see:
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"..."}}
If you see errors, check:
  1. Credentials exist: ls -la ~/.gmail-mcp/
  2. Credentials are valid JSON: cat ~/.gmail-mcp/credentials.json | jq
  3. OAuth keys are valid: cat ~/.gmail-mcp/gcp-oauth.keys.json | jq

Container Can’t Access Gmail

The container needs ~/.gmail-mcp mounted. Verify in src/container-runner.ts:
const gmailMcpDir = path.join(os.homedir(), '.gmail-mcp');
if (existsSync(gmailMcpDir)) {
  mounts.push(
    `-v`,
    `${gmailMcpDir}:/home/node/.gmail-mcp`
  );
}
Check container logs:
cat groups/main/logs/container-*.log | tail -50

Emails Not Being Detected (Channel Mode)

1

Check Filter Query

The default filter is is:unread category:primary. Emails in Promotions, Social, etc. are excluded.
2

Check Polling

Look for Gmail polling activity in logs:
tail -f logs/nanoclaw.log | grep -i gmail
3

Check Processed IDs

The channel tracks processed email IDs to avoid duplicates. If stuck, restart:
launchctl kickstart -k gui/$(id -u)/com.nanoclaw

“App isn’t verified” Warning

This is normal for personal OAuth apps. Click:
  1. Advanced
  2. Go to [app name] (unsafe)
  3. Allow
The warning appears because you haven’t submitted the app for Google’s verification process (which is only needed for public apps).

Implementation Details

Dependencies

  • googleapis - Google APIs client library (channel mode only)
  • @gongrzhe/server-gmail-autoauth-mcp - MCP server with Gmail tools (both modes)

JID Format

  • Channel mode: gmail:<thread-id> (e.g., gmail:18a1b2c3d4e5f6g7)

Self-Registration Code (Channel Mode)

registerChannel('gmail', (opts: ChannelOpts) => {
  const credDir = path.join(os.homedir(), '.gmail-mcp');
  const keysPath = path.join(credDir, 'gcp-oauth.keys.json');
  const tokensPath = path.join(credDir, 'credentials.json');
  
  if (!existsSync(keysPath) || !existsSync(tokensPath)) {
    logger.warn('Gmail credentials not found. Run /add-gmail to set up.');
    return null;
  }
  
  return new GmailChannel(opts);
});

Polling Logic

async pollForMessages(): Promise<void> {
  const response = await this.gmail.users.messages.list({
    userId: 'me',
    q: 'is:unread category:primary',
    maxResults: 10,
  });
  
  for (const message of response.data.messages || []) {
    if (this.processedIds.has(message.id!)) continue;
    
    const fullMessage = await this.gmail.users.messages.get({
      userId: 'me',
      id: message.id!,
      format: 'full',
    });
    
    // Extract headers, body, and deliver to agent
    this.processMessage(fullMessage);
    this.processedIds.add(message.id!);
  }
}

Error Backoff

If polling fails, the interval increases exponentially:
const backoffMs = Math.min(
  this.pollIntervalMs * Math.pow(2, this.consecutiveErrors),
  30 * 60 * 1000  // Max 30 minutes
);

Removal

Tool-Only Mode

  1. Remove ~/.gmail-mcp mount from src/container-runner.ts
  2. Remove gmail MCP server from container/agent-runner/src/index.ts
  3. Remove gmail from .nanoclaw/state.yaml
  4. Clear stale agent-runner: rm -r data/sessions/*/agent-runner-src 2>/dev/null || true
  5. Rebuild: cd container && ./build.sh && cd .. && npm run build
  6. Restart service

Channel Mode

  1. Delete src/channels/gmail.ts and src/channels/gmail.test.ts
  2. Remove import './gmail.js' from src/channels/index.ts
  3. Follow steps 1-6 from tool-only mode removal
  4. Uninstall: npm uninstall googleapis

Next Steps

Channel Overview

Learn about the channel system

WhatsApp Channel

Set up WhatsApp integration

Build docs developers (and LLMs) love