skills/email/SKILL.md
Gmail integration for reading, sending, and managing emails. Built on top of google-oauth for authentication, with SQLite caching, thread support, and full Gmail API capabilities.
npx skillsauth add ticruz38/skills emailInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Gmail integration for reading, sending, and managing emails through your agent. Built on top of the google-oauth skill for secure authentication.
npm install
npm run build
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
# Check connection status
node dist/cli.js status
# Check specific profile
node dist/cli.js status work
node dist/cli.js health
# List 20 most recent emails
node dist/cli.js list
# List specific number
node dist/cli.js list default 10
Use Gmail's powerful search syntax:
# Search by sender
node dist/cli.js search "from:[email protected]"
# Unread emails from sender
node dist/cli.js search "from:[email protected] 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"
node dist/cli.js read <message-id>
node dist/cli.js thread <thread-id>
node dist/cli.js send "[email protected]" "Subject" "Email body text"
node dist/cli.js reply <message-id> "Your reply text"
# 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>
node dist/cli.js labels
import { EmailSkill } from '@openclaw/email';
// Create skill for default profile
const email = new EmailSkill();
// Or for specific profile
const workEmail = EmailSkill.forProfile('work');
const status = await email.getStatus();
console.log('Connected:', status.connected);
console.log('Email:', status.email);
console.log('Has Gmail:', status.hasGmailScope);
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
});
const result = await email.search('from:[email protected] is:unread');
console.log(`Found ${result.resultSizeEstimate} emails`);
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);
}
const thread = await email.getThread('thread-id');
for (const msg of thread.messages) {
console.log(`${msg.from}: ${msg.bodyText.substring(0, 100)}...`);
}
// Simple text email
await email.send({
to: '[email protected]',
subject: 'Hello',
bodyText: 'This is the email body'
});
// With CC and HTML
await email.send({
to: ['[email protected]', '[email protected]'],
cc: '[email protected]',
subject: 'Meeting Notes',
bodyText: 'Plain text version',
bodyHtml: '<h1>Meeting Notes</h1><p>Details...</p>'
});
// With attachments
await email.send({
to: '[email protected]',
subject: 'Proposal',
bodyText: 'Please find attached the proposal.',
attachments: [
{
filename: 'proposal.pdf',
content: fs.readFileSync('proposal.pdf'),
mimeType: 'application/pdf'
}
]
});
await email.reply('message-id', {
bodyText: 'Thanks for your email!'
});
// 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');
const labels = await email.listLabels();
for (const label of labels) {
console.log(`${label.id}: ${label.name} (${label.type})`);
}
const health = await email.healthCheck();
if (health.status === 'healthy') {
console.log('Gmail API is accessible');
} else {
console.error('Issue:', health.message);
}
The search function supports Gmail's full query syntax:
| Query | Description |
|-------|-------------|
| from:[email protected] | Emails from specific sender |
| to:[email protected] | Emails to specific recipient |
| cc:[email protected] | 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:[email protected] is:unread - Unread from bosshas:attachment (filename:pdf OR filename:doc) - PDF or DOC attachmentssubject:meeting -from:[email protected] - Meeting emails not from calendarCached email metadata is stored in:
~/.openclaw/skills/email/cache.db
Tables:
email_metadata - Cached email headers and metadatasync_history - Sync state for incremental updatesManage 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
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);
}
}
# 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"
Authenticate with google-oauth first:
node ../google-oauth/dist/cli.js connect default gmail
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
Check health status:
node dist/cli.js health
@openclaw/google-oauth: For Gmail authentication@openclaw/auth-provider: Base authentication (via google-oauth)sqlite3: Local cachingtesting
Suggest recipes based on dietary preferences, available ingredients, and cuisine preferences
development
Extract data from receipt photos using Google Vision API
business
QuickBooks Online integration for accounting sync - sync customers, invoices, and transactions with two-way sync and conflict resolution
testing
QuickBooks OAuth adapter for QuickBooks Online accounting integration. Built on top of auth-provider for secure token management with automatic refresh, multi-profile support, sandbox/production toggle, and health checks.