email

SKILL.md

Email Skill

Gmail integration for reading, sending, and managing emails through your agent. Built on top of the google-oauth skill for secure authentication.

Features

  • Read & List Emails: Browse your inbox with pagination
  • Search: Full Gmail query syntax support
  • Send & Reply: Compose and reply to emails
  • Thread View: See entire conversation threads
  • Labels: View and filter by Gmail labels
  • Attachments: Download email attachments
  • Actions: Star, archive, trash, mark read/unread
  • SQLite Cache: Local metadata caching for performance

Installation

npm install
npm run build

Prerequisites

The email skill requires Gmail authorization through the google-oauth skill:

# Connect your Google account with Gmail scope
node ../google-oauth/dist/cli.js connect default gmail

CLI Usage

Check Status

# Check connection status
node dist/cli.js status

# Check specific profile
node dist/cli.js status work

Health Check

node dist/cli.js health

List Emails

# List 20 most recent emails
node dist/cli.js list

# List specific number
node dist/cli.js list default 10

Search Emails

Use Gmail's powerful search syntax:

# Search by sender
node dist/cli.js search "from:boss@company.com"

# Unread emails from sender
node dist/cli.js search "from:client@example.com is:unread"

# Emails with attachments
node dist/cli.js search "has:attachment filename:pdf"

# Recent important emails
node dist/cli.js search "is:important after:2024/01/01"

Read Email

node dist/cli.js read <message-id>

View Thread

node dist/cli.js thread <thread-id>

Send Email

node dist/cli.js send "recipient@example.com" "Subject" "Email body text"

Reply to Email

node dist/cli.js reply <message-id> "Your reply text"

Manage Emails

# Mark as read/unread
node dist/cli.js mark-read <message-id>
node dist/cli.js mark-unread <message-id>

# Star/unstar
node dist/cli.js star <message-id>
node dist/cli.js unstar <message-id>

# Archive (remove from inbox)
node dist/cli.js archive <message-id>

# Move to trash
node dist/cli.js trash <message-id>

# Delete permanently (careful!)
node dist/cli.js delete <message-id>

List Labels

node dist/cli.js labels

JavaScript/TypeScript API

Initialize

import { EmailSkill } from '@openclaw/email';

// Create skill for default profile
const email = new EmailSkill();

// Or for specific profile
const workEmail = EmailSkill.forProfile('work');

Check Status

const status = await email.getStatus();
console.log('Connected:', status.connected);
console.log('Email:', status.email);
console.log('Has Gmail:', status.hasGmailScope);

List Emails

const result = await email.list({ maxResults: 10 });

for (const msg of result.emails) {
  console.log(`${msg.id}: ${msg.subject}`);
  console.log(`  From: ${msg.from}`);
  console.log(`  Unread: ${msg.isUnread}`);
}

// Pagination
const nextPage = await email.list({
  maxResults: 10,
  pageToken: result.nextPageToken
});

Search Emails

const result = await email.search('from:boss@company.com is:unread');
console.log(`Found ${result.resultSizeEstimate} emails`);

Read Full Email

const fullEmail = await email.read('message-id');

console.log('Subject:', fullEmail.subject);
console.log('From:', fullEmail.from);
console.log('Body:', fullEmail.bodyText);
console.log('HTML:', fullEmail.bodyHtml);

// Attachments
for (const att of fullEmail.attachments) {
  console.log(`Attachment: ${att.filename} (${att.size} bytes)`);
  
  // Download
  const content = await email.getAttachment(fullEmail.id, att.id);
  fs.writeFileSync(att.filename, content);
}

View Thread

const thread = await email.getThread('thread-id');

for (const msg of thread.messages) {
  console.log(`${msg.from}: ${msg.bodyText.substring(0, 100)}...`);
}

Send Email

// Simple text email
await email.send({
  to: 'recipient@example.com',
  subject: 'Hello',
  bodyText: 'This is the email body'
});

// With CC and HTML
await email.send({
  to: ['user1@example.com', 'user2@example.com'],
  cc: 'manager@example.com',
  subject: 'Meeting Notes',
  bodyText: 'Plain text version',
  bodyHtml: '<h1>Meeting Notes</h1><p>Details...</p>'
});

// With attachments
await email.send({
  to: 'client@example.com',
  subject: 'Proposal',
  bodyText: 'Please find attached the proposal.',
  attachments: [
    {
      filename: 'proposal.pdf',
      content: fs.readFileSync('proposal.pdf'),
      mimeType: 'application/pdf'
    }
  ]
});

Reply to Email

await email.reply('message-id', {
  bodyText: 'Thanks for your email!'
});

Manage Emails

// Mark as read/unread
await email.markAsRead('message-id', true);
await email.markAsRead('message-id', false);

// Star/unstar
await email.star('message-id', true);
await email.star('message-id', false);

// Archive
await email.archive('message-id');

// Trash
await email.trash('message-id');

// Delete permanently
await email.delete('message-id');

List Labels

const labels = await email.listLabels();

for (const label of labels) {
  console.log(`${label.id}: ${label.name} (${label.type})`);
}

Health Check

const health = await email.healthCheck();

if (health.status === 'healthy') {
  console.log('Gmail API is accessible');
} else {
  console.error('Issue:', health.message);
}

Gmail Query Syntax

The search function supports Gmail's full query syntax:

Query Description
from:sender@example.com Emails from specific sender
to:recipient@example.com Emails to specific recipient
cc:someone@example.com CC'd emails
subject:meeting Subject contains "meeting"
has:attachment Has any attachment
filename:pdf Has PDF attachment
is:unread Unread emails
is:read Read emails
is:starred Starred emails
is:important Important emails
in:inbox In inbox
in:sent Sent emails
in:trash In trash
in:spam In spam
label:work With specific label
after:2024/01/01 After date
before:2024/12/31 Before date
older_than:1d Older than 1 day
newer_than:1w Newer than 1 week

Combine with operators:

  • from:boss@company.com is:unread - Unread from boss
  • has:attachment (filename:pdf OR filename:doc) - PDF or DOC attachments
  • subject:meeting -from:calendar@google.com - Meeting emails not from calendar

Storage

Cached email metadata is stored in:

~/.openclaw/skills/email/cache.db

Tables:

  • email_metadata - Cached email headers and metadata
  • sync_history - Sync state for incremental updates

Multi-Profile Support

Manage multiple Gmail accounts:

import { EmailSkill } from '@openclaw/email';

// Work account
const work = EmailSkill.forProfile('work');

// Personal account  
const personal = EmailSkill.forProfile('personal');

// Use independently
const workEmails = await work.list({ maxResults: 10 });
const personalEmails = await personal.list({ maxResults: 10 });

Each profile needs separate authentication:

node ../google-oauth/dist/cli.js connect work gmail
node ../google-oauth/dist/cli.js connect personal gmail

Error Handling

try {
  const emails = await email.list();
} catch (error) {
  if (error.message.includes('Not connected')) {
    console.log('Please authenticate first');
  } else if (error.message.includes('Gmail scope')) {
    console.log('Re-authenticate with Gmail permissions');
  } else {
    console.error('Error:', error.message);
  }
}

Testing

# Type checking
npm run typecheck

# Build
npm run build

# Check status
npm run status

# List recent emails
npm run cli -- list default 5

# Search
npm run cli -- search "is:unread"

Troubleshooting

"Not connected" error

Authenticate with google-oauth first:

node ../google-oauth/dist/cli.js connect default gmail

"Gmail scope not authorized"

Your Google account is connected but without Gmail permissions. Reconnect:

node ../google-oauth/dist/cli.js disconnect default
node ../google-oauth/dist/cli.js connect default gmail

API errors

Check health status:

node dist/cli.js health

Dependencies

  • @openclaw/google-oauth: For Gmail authentication
  • @openclaw/auth-provider: Base authentication (via google-oauth)
  • sqlite3: Local caching

Security Notes

  • OAuth tokens stored encrypted by auth-provider
  • Cache database has 0600 permissions (user read/write only)
  • No email content stored locally (only metadata cached)
  • Uses Gmail API with least-privilege scope
Weekly Installs
3
Repository
ticruz38/skills
First Seen
Feb 12, 2026
Installed on
openclaw2
codex2
mcpjam1
claude-code1
windsurf1
zencoder1