@clawhub-cehd5170-d5f2b7cce2
Send, read, search, and manage Gmail emails via the Gmail REST API. Use when asked to send an email, check inbox, read messages, search mail, reply to emails...
---
name: gmail
description: "Send, read, search, and manage Gmail emails via the Gmail REST API. Use when asked to send an email, check inbox, read messages, search mail, reply to emails, draft emails, or manage labels. Triggers on phrases like 'send an email', 'check my email', 'reply to', 'draft a message', 'search my inbox', 'read my latest emails', 'send a gmail'."
user-invocable: true
metadata:
{
"openclaw":
{
"emoji": "📧",
"requires": { "bins": ["curl", "jq"], "env": ["GMAIL_ACCESS_TOKEN"] },
},
}
---
# Gmail Skill
Send, read, search, reply, and manage Gmail directly from OpenClaw.
## Setup
### Option 1: OAuth2 Access Token (recommended)
1. Create OAuth2 credentials at https://console.cloud.google.com/apis/credentials
2. Enable the Gmail API at https://console.cloud.google.com/apis/library/gmail.googleapis.com
3. Obtain an access token via OAuth2 flow with scopes:
- `https://www.googleapis.com/auth/gmail.send`
- `https://www.googleapis.com/auth/gmail.readonly`
- `https://www.googleapis.com/auth/gmail.modify`
4. Set the environment variable:
```bash
export GMAIL_ACCESS_TOKEN="your-access-token"
```
### Option 2: Refresh Token (long-lived)
If you have a refresh token, set these additional variables:
```bash
export GMAIL_CLIENT_ID="your-client-id"
export GMAIL_CLIENT_SECRET="your-client-secret"
export GMAIL_REFRESH_TOKEN="your-refresh-token"
```
All API calls use Bearer auth:
```bash
curl -s -H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/..."
```
### Token Refresh
If `GMAIL_REFRESH_TOKEN` is set, refresh the access token before any API call:
```bash
GMAIL_ACCESS_TOKEN=$(curl -s -X POST "https://oauth2.googleapis.com/token" \
-d "client_id=$GMAIL_CLIENT_ID" \
-d "client_secret=$GMAIL_CLIENT_SECRET" \
-d "refresh_token=$GMAIL_REFRESH_TOKEN" \
-d "grant_type=refresh_token" | jq -r '.access_token')
```
---
## Commands
### Send an Email
Parse the user's request for:
| Field | Required | Description |
|-------|----------|-------------|
| to | yes | Recipient email address(es), comma-separated |
| subject | yes | Email subject line |
| body | yes | Email body (plain text or HTML) |
| cc | no | CC recipients |
| bcc | no | BCC recipients |
| attachments | no | File paths to attach |
| research | no | Topic to web-search for enriching the email body |
#### Web Research (if applicable)
If the user wants research-informed content:
1. Use `web_search` to find relevant information.
2. Fetch key pages with `xurl` or `curl` for details.
3. Incorporate findings into the email body naturally.
#### Compose and Send
Build a raw RFC 2822 message and base64url-encode it:
```bash
# Build raw message
RAW_MESSAGE=$(cat <<'MSGEOF'
From: me
To: [email protected]
Subject: Email subject
Content-Type: text/html; charset="UTF-8"
MIME-Version: 1.0
<html><body>
<p>Email body here.</p>
</body></html>
MSGEOF
)
# Base64url encode (no padding, URL-safe)
ENCODED=$(echo -n "$RAW_MESSAGE" | base64 -w 0 | tr '+/' '-_' | tr -d '=')
# Send
curl -s -X POST \
-H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/send" \
-d "{\"raw\": \"$ENCODED\"}"
```
Extract the message ID from the response and report success.
---
### Read / Check Inbox
List recent messages:
```bash
# List latest messages (default 10)
curl -s -H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=10&labelIds=INBOX"
```
For each message, fetch full details:
```bash
curl -s -H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/{MESSAGE_ID}?format=full"
```
Extract and display:
- **From**: sender
- **Subject**: subject line
- **Date**: when received
- **Snippet**: preview text
- **Body**: decoded from base64 (plain text part preferred)
Decode the body:
```bash
# Extract plain text body from payload parts
BODY=$(echo "$RESPONSE" | jq -r '
.payload.parts[]? |
select(.mimeType == "text/plain") |
.body.data' | base64 -d 2>/dev/null)
# Fallback: single-part message
if [ -z "$BODY" ]; then
BODY=$(echo "$RESPONSE" | jq -r '.payload.body.data' | base64 -d 2>/dev/null)
fi
```
---
### Search Emails
```bash
# Search with Gmail query syntax
curl -s -H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages?q=QUERY&maxResults=10"
```
Gmail query examples:
- `from:[email protected]` — from a specific sender
- `subject:invoice` — subject contains "invoice"
- `is:unread` — unread messages
- `after:2026/01/01 before:2026/03/01` — date range
- `has:attachment filename:pdf` — PDFs attached
- `label:important` — labeled important
Fetch and display matching messages using the same read flow above.
---
### Reply to an Email
1. Fetch the original message to get `threadId`, `Message-ID` header, and sender.
2. Build the reply with proper headers:
```bash
RAW_REPLY=$(cat <<'MSGEOF'
From: me
To: [email protected]
Subject: Re: Original subject
In-Reply-To: <[email protected]>
References: <[email protected]>
Content-Type: text/plain; charset="UTF-8"
MIME-Version: 1.0
Reply body here.
MSGEOF
)
ENCODED=$(echo -n "$RAW_REPLY" | base64 -w 0 | tr '+/' '-_' | tr -d '=')
curl -s -X POST \
-H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/send" \
-d "{\"raw\": \"$ENCODED\", \"threadId\": \"THREAD_ID\"}"
```
---
### Draft an Email
Create a draft without sending:
```bash
ENCODED=$(echo -n "$RAW_MESSAGE" | base64 -w 0 | tr '+/' '-_' | tr -d '=')
curl -s -X POST \
-H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://gmail.googleapis.com/gmail/v1/users/me/drafts" \
-d "{\"message\": {\"raw\": \"$ENCODED\"}}"
```
Report the draft ID so the user can review it in Gmail before sending.
---
### Manage Labels
```bash
# List all labels
curl -s -H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
"https://gmail.googleapis.com/gmail/v1/users/me/labels" | jq '.labels[] | {name, id}'
# Add label to a message
curl -s -X POST \
-H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/{MESSAGE_ID}/modify" \
-d '{"addLabelIds": ["LABEL_ID"]}'
# Remove label
curl -s -X POST \
-H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/{MESSAGE_ID}/modify" \
-d '{"removeLabelIds": ["LABEL_ID"]}'
# Mark as read
curl -s -X POST \
-H "Authorization: Bearer $GMAIL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://gmail.googleapis.com/gmail/v1/users/me/messages/{MESSAGE_ID}/modify" \
-d '{"removeLabelIds": ["UNREAD"]}'
```
---
## Network Policy
To use this skill inside NemoClaw's sandbox, add a Gmail network policy preset. Create or add to your sandbox policy:
```yaml
network_policies:
google_gmail:
name: google_gmail
endpoints:
- host: gmail.googleapis.com
port: 443
protocol: rest
enforcement: enforce
tls: terminate
rules:
- allow: { method: GET, path: "/**" }
- allow: { method: POST, path: "/**" }
- host: oauth2.googleapis.com
port: 443
protocol: rest
enforcement: enforce
tls: terminate
rules:
- allow: { method: POST, path: "/token" }
- host: accounts.google.com
port: 443
protocol: rest
enforcement: enforce
tls: terminate
rules:
- allow: { method: GET, path: "/**" }
- allow: { method: POST, path: "/**" }
```
---
## Notes
- Gmail API rate limit: 250 quota units per second per user.
- Sending has a daily limit of 2,000 messages for Google Workspace, 500 for free Gmail.
- Access tokens expire after ~1 hour. Use refresh token flow for long-running sessions.
- Attachments require multipart MIME encoding — for large files, use the resumable upload endpoint.
## Examples
```
# Send a simple email
/gmail send to:[email protected] subject:"Standup notes" body:"Here are today's updates..."
# Check inbox
/gmail inbox
# Search for unread emails from a specific sender
/gmail search "from:[email protected] is:unread"
# Reply to the latest email
/gmail reply latest "Thanks, I'll look into this."
# Draft an email with web research
/gmail draft to:[email protected] subject:"Redis 8 migration plan" --search "Redis 8 breaking changes"
# Mark all unread as read
/gmail read-all
```
Create Jira tickets with web-researched content. Use when asked to create, file, or open a Jira issue/ticket/story/bug/task, especially when the ticket conte...
---
name: jira-ticket
description: "Create Jira tickets with web-researched content. Use when asked to create, file, or open a Jira issue/ticket/story/bug/task, especially when the ticket content should be informed by web research or search results. Triggers on phrases like 'create a Jira ticket', 'file a Jira issue', 'open a bug in Jira', 'make a Jira story with research'."
user-invocable: true
metadata:
{
"openclaw":
{
"emoji": "🎫",
"requires": { "bins": ["curl", "jq"], "env": ["JIRA_API_TOKEN", "JIRA_EMAIL", "JIRA_BASE_URL"] },
},
}
---
# Jira Ticket Creator with Web Research
Create Jira tickets whose content is enriched by web search. Follow these phases in order.
## Setup
Three environment variables are required:
- `JIRA_BASE_URL` — your Atlassian instance (e.g. `https://yourteam.atlassian.net`)
- `JIRA_EMAIL` — the email tied to your Atlassian account
- `JIRA_API_TOKEN` — an API token from https://id.atlassian.com/manage-profile/security/api-tokens
All Jira API calls use Basic auth via `curl -u` and force HTTP/1.1:
```bash
curl --http1.1 -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" "$JIRA_BASE_URL/rest/api/3/..."
```
---
## Phase 1 — Parse Arguments
Parse the user's request to extract:
| Field | Required | Description |
|-------|----------|-------------|
| project | yes | Jira project key (e.g. `ENG`, `OPS`) |
| issuetype | no | `Task`, `Bug`, `Story`, `Epic` (default: `Task`) |
| summary | yes | Short title for the ticket |
| search_query | no | Topic to web-search for enriching the description |
| priority | no | `Highest`, `High`, `Medium`, `Low`, `Lowest` (default: `Medium`) |
| assignee | no | Atlassian account email or ID |
| labels | no | Comma-separated labels |
| components | no | Comma-separated component names |
If the user does not provide a project key, ask for it before proceeding.
---
## Phase 2 — Web Research (if applicable)
If the user asked for research, or if the ticket would benefit from context (e.g. a bug report referencing an external API, a story about integrating a third-party service):
1. Use the `web_search` tool to search for the relevant topic.
2. Use the `xurl` tool or `curl` to fetch key pages for details.
3. Extract the most relevant information: error descriptions, API docs, best practices, version notes, or solution approaches.
Compile findings into a structured summary:
```
### Research Summary
- **Source**: [URL]
- **Key findings**: ...
- **Relevant details**: ...
```
If no research is needed, skip to Phase 3.
---
## Phase 3 — Compose Ticket Content
Build the ticket description in Atlassian Document Format (ADF). Combine:
- The user's original request/context
- Research findings from Phase 2 (if any)
- Acceptance criteria (when creating Stories)
- Steps to reproduce (when creating Bugs)
Keep the description concise and actionable.
### ADF Structure
Jira API v3 uses ADF for the description field. Minimal example:
```json
{
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Description text here." }]
}
]
}
```
For richer formatting (headings, bullet lists, links):
```json
{
"type": "doc",
"version": 1,
"content": [
{
"type": "heading",
"attrs": { "level": 3 },
"content": [{ "type": "text", "text": "Summary" }]
},
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [{ "type": "text", "text": "Item one" }]
}
]
}
]
},
{
"type": "paragraph",
"content": [
{ "type": "text", "text": "Source: " },
{
"type": "text",
"text": "link text",
"marks": [{ "type": "link", "attrs": { "href": "https://example.com" } }]
}
]
}
]
}
```
---
## Phase 4 — Validate Project and Fields
Before creating the ticket, verify the project exists and discover available fields:
```bash
# Verify project
curl --http1.1 -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \
"$JIRA_BASE_URL/rest/api/3/project/$PROJECT_KEY" | jq '{key, name, id}'
# List available issue types for the project
curl --http1.1 -s -u "$JIRA_EMAIL:$JIRA_API_TOKEN" -H "Content-Type: application/json" \
"$JIRA_BASE_URL/rest/api/3/project/$PROJECT_KEY/statuses" | jq '.[].name'
```
If the project or issue type is invalid, report the error and ask the user to correct it.
---
## Phase 5 — Create the Ticket
```bash
curl --http1.1 -s -X POST \
-u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-H "Content-Type: application/json" \
"$JIRA_BASE_URL/rest/api/3/issue" \
-d '{
"fields": {
"project": { "key": "PROJECT_KEY" },
"summary": "Ticket summary here",
"issuetype": { "name": "Task" },
"priority": { "name": "Medium" },
"description": { ADF_OBJECT },
"labels": ["label1", "label2"]
}
}'
```
Extract the response:
```bash
# Parse response for issue key and URL
ISSUE_KEY=$(echo "$RESPONSE" | jq -r '.key')
ISSUE_URL="$JIRA_BASE_URL/browse/$ISSUE_KEY"
```
If the API returns an error, display the error message and suggest corrections.
---
## Phase 6 — Report
Present the result to the user:
- **Issue key**: e.g. `ENG-1234`
- **URL**: direct link to the ticket
- **Summary**: the title that was set
- **Research included**: yes/no, with sources listed
---
## Notes
- The Jira REST API v3 requires ADF for descriptions — plain text or markdown will be rejected.
- Rate limits: Jira Cloud allows ~100 requests per minute per user.
- The `jira.yaml` network policy preset in NemoClaw already allows `*.atlassian.net`, `auth.atlassian.com`, and `api.atlassian.com` on port 443.
- To use this skill inside NemoClaw's sandbox, enable the Jira preset in your sandbox policy.
## Examples
```
# Create a simple task
/jira-ticket ENG "Update API rate limiting docs"
# Create a bug with web research
/jira-ticket ENG --type Bug --search "Node.js fetch timeout ECONNRESET" "Fix intermittent ECONNRESET in payment service"
# Create a story with priority and labels
/jira-ticket PLATFORM --type Story --priority High --labels "q2,backend" "Add OAuth2 PKCE flow for mobile clients"
```