add-gmail

Add Gmail Integration

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "add-gmail" with this command: npx skills add creatuluw/nanoclaw/creatuluw-nanoclaw-add-gmail

Add Gmail Integration

This skill adds Gmail capabilities to NanoClaw. It can be configured in two modes:

  • Tool Mode - Agent can read/send emails, but only when triggered from WhatsApp

  • Channel Mode - Emails can trigger the agent, schedule tasks, and receive email replies

Initial Questions

Ask the user:

How do you want to use Gmail with NanoClaw?

Option 1: Tool Mode

  • Agent can read and send emails when you ask it to

  • Triggered only from WhatsApp (e.g., "@Andy check my email" or "@Andy send an email to...")

  • Simpler setup, no email polling

Option 2: Channel Mode

  • Everything in Tool Mode, plus:

  • Emails to a specific address/label trigger the agent

  • Agent replies via email (not WhatsApp)

  • Can schedule tasks via email

  • Requires email polling infrastructure

Store their choice and proceed to the appropriate section.

Prerequisites (Both Modes)

  1. Check Existing Gmail Setup

First, check if Gmail is already configured:

ls -la ~/.gmail-mcp/ 2>/dev/null || echo "No Gmail config found"

If credentials.json exists, skip to "Verify Gmail Access" below.

  1. Create Gmail Config Directory

mkdir -p ~/.gmail-mcp

  1. GCP Project Setup

USER ACTION REQUIRED

Tell the user:

I need you to set up Google Cloud OAuth credentials. I'll walk you through it:

Wait for user confirmation, then continue:

  • Now enable the Gmail API:

  • In the left sidebar, go to APIs & Services → Library

  • Search for "Gmail API"

  • Click on it, then click Enable

Wait for user confirmation, then continue:

  • Now create OAuth credentials:

  • Go to APIs & Services → Credentials (in the left sidebar)

  • Click + CREATE CREDENTIALS at the top

  • Select OAuth client ID

  • If prompted for consent screen, choose "External", fill in app name (e.g., "NanoClaw"), your email, and save

  • For Application type, select Desktop app

  • Name it anything (e.g., "NanoClaw Gmail")

  • Click Create

Wait for user confirmation, then continue:

  • Download the credentials:

  • Click DOWNLOAD JSON on the popup (or find it in the credentials list and click the download icon)

  • Save it as gcp-oauth.keys.json

Where did you save the file? (Give me the full path, or just paste the file contents here)

If user provides a path, copy it:

cp "/path/user/provided/gcp-oauth.keys.json" ~/.gmail-mcp/gcp-oauth.keys.json

If user pastes the JSON content, write it directly:

cat > ~/.gmail-mcp/gcp-oauth.keys.json << 'EOF' {paste the JSON here} EOF

Verify the file is valid JSON:

cat ~/.gmail-mcp/gcp-oauth.keys.json | head -5

  1. OAuth Authorization

USER ACTION REQUIRED

Tell the user:

I'm going to run the Gmail authorization. A browser window will open asking you to sign in to Google and grant access.

Important: If you see a warning that the app isn't verified, click "Advanced" then "Go to [app name] (unsafe)" - this is normal for personal OAuth apps.

Run the authorization:

npx -y @gongrzhe/server-gmail-autoauth-mcp auth

If that doesn't work (some versions don't have an auth subcommand), run it and let it prompt:

timeout 60 npx -y @gongrzhe/server-gmail-autoauth-mcp || true

Tell user:

Complete the authorization in your browser. The window should close automatically when done. Let me know when you've authorized.

  1. Verify Gmail Access

Check that credentials were saved:

if [ -f ~/.gmail-mcp/credentials.json ]; then echo "Gmail authorization successful!" ls -la ~/.gmail-mcp/ else echo "ERROR: credentials.json not found - authorization may have failed" fi

Test the connection by listing labels (quick sanity check):

echo '{"method": "tools/list"}' | timeout 10 npx -y @gongrzhe/server-gmail-autoauth-mcp 2>/dev/null | head -20 || echo "MCP responded (check output above)"

If everything works, proceed to implementation.

Tool Mode Implementation

For Tool Mode, integrate Gmail MCP into the agent runner. Execute these changes directly.

Step 1: Add Gmail MCP to Agent Runner

Read container/agent-runner/src/index.ts and find the mcpServers config in the query() call.

Add gmail to the mcpServers object:

gmail: { command: 'npx', args: ['-y', '@gongrzhe/server-gmail-autoauth-mcp'] }

Find the allowedTools array and add Gmail tools:

'mcp__gmail__*'

The result should look like:

mcpServers: { nanoclaw: ipcMcp, gmail: { command: 'npx', args: ['-y', '@gongrzhe/server-gmail-autoauth-mcp'] } }, allowedTools: [ 'Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep', 'WebSearch', 'WebFetch', 'mcp__nanoclaw__', 'mcp__gmail__' ],

Step 2: Mount Gmail Credentials in Container

Read src/container-runner.ts and find the buildVolumeMounts function.

Add this mount block (after the .claude mount is a good location):

// Gmail credentials directory const gmailDir = path.join(homeDir, '.gmail-mcp'); if (fs.existsSync(gmailDir)) { mounts.push({ hostPath: gmailDir, containerPath: '/home/node/.gmail-mcp', readonly: false // MCP may need to refresh tokens }); }

Step 3: Update Group Memory

Append to groups/CLAUDE.md (the global memory file):

Email (Gmail)

You have access to Gmail via MCP tools:

  • mcp__gmail__search_emails - Search emails with query
  • mcp__gmail__get_email - Get full email content by ID
  • mcp__gmail__send_email - Send an email
  • mcp__gmail__draft_email - Create a draft
  • mcp__gmail__list_labels - List available labels

Example: "Check my unread emails from today" or "Send an email to john@example.com about the meeting"

Also append the same section to groups/main/CLAUDE.md .

Step 4: Rebuild and Restart

Run these commands:

cd container && ./build.sh

Wait for container build to complete, then:

cd .. && npm run build

Wait for TypeScript compilation, then restart the service:

launchctl kickstart -k gui/$(id -u)/com.nanoclaw

Check that it started:

sleep 2 && launchctl list | grep nanoclaw

Step 5: Test Gmail Integration

Tell the user:

Gmail integration is set up! Test it by sending this message in your WhatsApp main channel:

@Andy check my recent emails

Or:

@Andy list my Gmail labels

Watch the logs for any errors:

tail -f logs/nanoclaw.log

Channel Mode Implementation

Channel Mode includes everything from Tool Mode, plus email polling and routing.

Additional Questions for Channel Mode

Ask the user:

How should the agent be triggered from email?

Option A: Specific Label

  • Create a Gmail label (e.g., "NanoClaw")

  • Emails with this label trigger the agent

  • You manually label emails or set up Gmail filters

Option B: Email Address Pattern

  • Emails to a specific address pattern (e.g., andy+task@gmail.com)

  • Uses Gmail's plus-addressing feature

Option C: Subject Prefix

  • Emails with a subject starting with a keyword (e.g., "[Andy]")

  • Anyone can trigger the agent by using the prefix

Also ask:

How should email conversations be grouped?

Option A: Per Email Thread

  • Each email thread gets its own conversation context

  • Agent remembers the thread history

Option B: Per Sender

  • All emails from the same sender share context

  • Agent remembers all interactions with that person

Option C: Single Context

  • All emails share the main group context

  • Like an additional input to the main channel

Store their choices for implementation.

Step 1: Complete Tool Mode First

Complete all Tool Mode steps above before continuing. Verify Gmail tools work by having the user test @Andy check my recent emails .

Step 2: Add Email Polling Configuration

Read src/types.ts and add this interface:

export interface EmailChannelConfig { enabled: boolean; triggerMode: 'label' | 'address' | 'subject'; triggerValue: string; // Label name, address pattern, or subject prefix contextMode: 'thread' | 'sender' | 'single'; pollIntervalMs: number; replyPrefix?: string; // Optional prefix for replies }

Read src/config.ts and add this configuration (customize values based on user's earlier answers):

export const EMAIL_CHANNEL: EmailChannelConfig = { enabled: true, triggerMode: 'label', // or 'address' or 'subject' triggerValue: 'NanoClaw', // the label name, address pattern, or prefix contextMode: 'thread', pollIntervalMs: 60000, // Check every minute replyPrefix: '[Andy] ' };

Step 3: Add Email State Tracking

Read src/db.ts and add these functions for tracking processed emails:

// Track processed emails to avoid duplicates export function initEmailTable(): void { db.exec( CREATE TABLE IF NOT EXISTS processed_emails ( message_id TEXT PRIMARY KEY, thread_id TEXT NOT NULL, sender TEXT NOT NULL, subject TEXT, processed_at TEXT NOT NULL, response_sent INTEGER DEFAULT 0 ) ); }

export function isEmailProcessed(messageId: string): boolean { const row = db.prepare('SELECT 1 FROM processed_emails WHERE message_id = ?').get(messageId); return !!row; }

export function markEmailProcessed(messageId: string, threadId: string, sender: string, subject: string): void { db.prepare( INSERT OR REPLACE INTO processed_emails (message_id, thread_id, sender, subject, processed_at) VALUES (?, ?, ?, ?, ?) ).run(messageId, threadId, sender, subject, new Date().toISOString()); }

export function markEmailResponded(messageId: string): void { db.prepare('UPDATE processed_emails SET response_sent = 1 WHERE message_id = ?').run(messageId); }

Also find the initDatabase() function in src/db.ts and add a call to initEmailTable() .

Step 4: Create Email Channel Module

Create a new file src/email-channel.ts with this content:

import { EMAIL_CHANNEL } from './config.js'; import { isEmailProcessed, markEmailProcessed, markEmailResponded } from './db.js'; import pino from 'pino';

const logger = pino({ level: process.env.LOG_LEVEL || 'info', transport: { target: 'pino-pretty', options: { colorize: true } } });

interface EmailMessage { id: string; threadId: string; from: string; subject: string; body: string; date: string; }

// Gmail MCP client functions (call via subprocess or import the MCP directly) // These would invoke the Gmail MCP tools

export async function checkForNewEmails(): Promise<EmailMessage[]> { // Build query based on trigger mode let query: string; switch (EMAIL_CHANNEL.triggerMode) { case 'label': query = label:${EMAIL_CHANNEL.triggerValue} is:unread; break; case 'address': query = to:${EMAIL_CHANNEL.triggerValue} is:unread; break; case 'subject': query = subject:${EMAIL_CHANNEL.triggerValue} is:unread; break; }

// This requires calling Gmail MCP's search_emails tool // Implementation depends on how you want to invoke MCP from Node // Option 1: Use @anthropic-ai/claude-agent-sdk with just gmail MCP // Option 2: Run npx gmail MCP as subprocess and parse output // Option 3: Import gmail-autoauth-mcp directly

// Placeholder - implement based on preference return []; }

export async function sendEmailReply( threadId: string, to: string, subject: string, body: string ): Promise<void> { // Call Gmail MCP's send_email tool with in_reply_to for threading // Prefix subject with replyPrefix if configured const replySubject = subject.startsWith('Re:') ? subject : Re: ${subject};

const prefixedBody = EMAIL_CHANNEL.replyPrefix ? ${EMAIL_CHANNEL.replyPrefix}${body} : body;

// Implementation: invoke Gmail MCP send_email }

export function getContextKey(email: EmailMessage): string { switch (EMAIL_CHANNEL.contextMode) { case 'thread': return email-thread-${email.threadId}; case 'sender': return email-sender-${email.from.toLowerCase()}; case 'single': return 'email-main'; } }

Step 5: Add Email Polling to Main Loop

Read src/index.ts and add the email polling infrastructure. First, add these imports at the top:

import { checkForNewEmails, sendEmailReply, getContextKey } from './email-channel.js'; import { EMAIL_CHANNEL } from './config.js'; import { isEmailProcessed, markEmailProcessed, markEmailResponded } from './db.js';

async function startEmailLoop(): Promise<void> { if (!EMAIL_CHANNEL.enabled) { logger.info('Email channel disabled'); return; }

logger.info(Email channel running (trigger: ${EMAIL_CHANNEL.triggerMode}:${EMAIL_CHANNEL.triggerValue}));

while (true) { try { const emails = await checkForNewEmails();

  for (const email of emails) {
    if (isEmailProcessed(email.id)) continue;

    logger.info({ from: email.from, subject: email.subject }, 'Processing email');
    markEmailProcessed(email.id, email.threadId, email.from, email.subject);

    // Determine which group/context to use
    const contextKey = getContextKey(email);

    // Build prompt with email content
    const prompt = `&#x3C;email>

<from>${email.from}</from> <subject>${email.subject}</subject> <body>${email.body}</body> </email>

Respond to this email. Your response will be sent as an email reply.`;

    // Run agent with email context
    // You'll need to create a registered group for email or use a special handler
    const response = await runEmailAgent(contextKey, prompt, email);

    if (response) {
      await sendEmailReply(email.threadId, email.from, email.subject, response);
      markEmailResponded(email.id);
      logger.info({ to: email.from }, 'Email reply sent');
    }
  }
} catch (err) {
  logger.error({ err }, 'Error in email loop');
}

await new Promise(resolve => setTimeout(resolve, EMAIL_CHANNEL.pollIntervalMs));

} }

Then find the connectWhatsApp function and add startEmailLoop() call after startMessageLoop():

// In the connection === 'open' block, after startMessageLoop():
startEmailLoop();

Step 6: Implement Email Agent Runner

Add this function to src/index.ts
 (or create a separate src/email-agent.ts
 if preferred):

async function runEmailAgent(
  contextKey: string,
  prompt: string,
  email: EmailMessage
): Promise&#x3C;string | null> {
  // Email uses either:
  // 1. A dedicated "email" group folder
  // 2. Or dynamic folders per thread/sender

  const groupFolder = EMAIL_CHANNEL.contextMode === 'single'
    ? 'main'  // Use main group context
    : `email/${contextKey}`;  // Isolated email context

  // Ensure folder exists
  const groupDir = path.join(GROUPS_DIR, groupFolder);
  fs.mkdirSync(groupDir, { recursive: true });

  // Create minimal registered group for email
  const emailGroup: RegisteredGroup = {
    name: contextKey,
    folder: groupFolder,
    trigger: '',  // No trigger for email
    added_at: new Date().toISOString()
  };

  // Use existing runContainerAgent
  const output = await runContainerAgent(emailGroup, {
    prompt,
    sessionId: sessions[groupFolder],
    groupFolder,
    chatJid: `email:${email.from}`,  // Use email: prefix for JID
    isMain: false,
    isScheduledTask: false
  });

  if (output.newSessionId) {
    sessions[groupFolder] = output.newSessionId;
    saveJson(path.join(DATA_DIR, 'sessions.json'), sessions);
  }

  return output.status === 'success' ? output.result : null;
}

Step 7: Update IPC for Email Responses (Optional)

If you want the agent to be able to send emails proactively from within a session, read container/agent-runner/src/ipc-mcp.ts
 and add this tool:

// Add to the MCP tools
{
  name: 'send_email_reply',
  description: 'Send an email reply in the current thread',
  inputSchema: {
    type: 'object',
    properties: {
      body: { type: 'string', description: 'Email body content' }
    },
    required: ['body']
  }
}

Then add handling in src/index.ts
 in the processTaskIpc
 function or create a new IPC handler for email actions.

Step 8: Create Email Group Memory

Create the email group directory and memory file:

mkdir -p groups/email

Write groups/email/CLAUDE.md
:

# Email Channel

You are responding to emails. Your responses will be sent as email replies.

## Guidelines

- Be professional and clear
- Keep responses concise but complete
- Use proper email formatting (greetings, sign-off)
- If the email requires action you can't take, explain what the user should do

## Context

Each email thread or sender (depending on configuration) has its own conversation history.

Step 9: Rebuild and Test

Rebuild the container (required since agent-runner changed):

cd container &#x26;&#x26; ./build.sh

Wait for build to complete, then compile TypeScript:

cd .. &#x26;&#x26; npm run build

Restart the service:

launchctl kickstart -k gui/$(id -u)/com.nanoclaw

Verify it started and check for email channel startup message:

sleep 3 &#x26;&#x26; tail -20 logs/nanoclaw.log | grep -i email

Tell the user:

Email channel is now active! Test it by sending an email that matches your trigger:

- Label mode: Apply the "${triggerValue}" label to any email

- Address mode: Send an email to ${triggerValue}

- Subject mode: Send an email with subject starting with "${triggerValue}"

The agent should process it within a minute and send a reply.

Monitor for the test:

tail -f logs/nanoclaw.log | grep -E "(email|Email)"

Troubleshooting

Gmail MCP not responding

# Test Gmail MCP directly
npx -y @gongrzhe/server-gmail-autoauth-mcp

OAuth token expired

# Re-authorize
rm ~/.gmail-mcp/credentials.json
npx -y @gongrzhe/server-gmail-autoauth-mcp

Emails not being detected

- Check the trigger configuration matches your test email

- Verify the label exists (for label mode)

- Check processed_emails
 table for already-processed emails

Container can't access Gmail

- Verify ~/.gmail-mcp
 is mounted in container

- Check container logs: cat groups/main/logs/container-*.log | tail -50

Removing Gmail Integration

To remove Gmail entirely:

- 
Remove from container/agent-runner/src/index.ts
:

- Delete gmail
 from mcpServers

- Remove mcp__gmail__*
 from allowedTools

- 
Remove from src/container-runner.ts
:

- Delete the ~/.gmail-mcp
 mount block

- 
Remove from src/index.ts
 (Channel Mode only):

- Delete startEmailLoop()
 call

- Delete email-related imports

- 
Delete src/email-channel.ts
 (if created)

- 
Remove Gmail sections from groups/*/CLAUDE.md

- 
Rebuild:

cd container &#x26;&#x26; ./build.sh &#x26;&#x26; cd ..
npm run build
launchctl kickstart -k gui/$(id -u)/com.nanoclaw

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

add-voice-transcription

No summary provided by upstream source.

Repository SourceNeeds Review
General

customize

No summary provided by upstream source.

Repository SourceNeeds Review
General

setup

No summary provided by upstream source.

Repository SourceNeeds Review
General

convert-to-docker

No summary provided by upstream source.

Repository SourceNeeds Review