| name | permission-slip-openclaw-skill-gmail |
| description | Check, search, read, send, and archive Gmail through a Google account connected to Permission Slip. Use when the user says things like "check my email", "any new mail?", "search my inbox for X", "read that message", "reply to so-and-so", or "archive it" and their Gmail is managed via Permission Slip. |
Gmail (via Permission Slip)
This skill lets you act on the user's Gmail mailbox by driving the
permission-slip CLI. You never talk to the Gmail API directly — every action
goes through Permission Slip, which enforces the user's approval policy.
This skill contains no code of its own: it is a thin shim over the
permission-slip CLI. All authentication (Google OAuth), approval enforcement,
and default-account selection live in Permission Slip and its built-in google
connector.
Defaults
- "Check my email" → list the inbox, newest 50 messages.
Run
list_emails with {"query": "in:inbox", "max_results": 50}.
- Account selection: omit
--instance. Permission Slip auto-selects the
user's default Google instance. Only pass --instance if the user
explicitly names a second account.
Preflight (run once per session, before the first action)
permission-slip whoami — confirm this agent is registered. If not, tell
the user to register (permission-slip register ...) and stop.
permission-slip connectors — confirm google is available. If it's
missing, the user hasn't connected a Google account yet; point them at the
connector setup docs and stop.
Intent -> action mapping
| User says | Action | Params |
|---|
| "check my email", "any new mail?" | google.list_emails | {"query":"in:inbox","max_results":50} |
| "any unread mail?" | google.list_emails | {"query":"in:inbox is:unread","max_results":50} |
| "search for X from Y" | google.list_emails | {"query":"<gmail search>","max_results":50} |
| "read that one" / "open it" | google.read_email | {"message_id": <id>} |
| "reply to it" | google.send_email_reply | reply params (needs approval) |
| "send an email to ..." | google.send_email | send params (needs approval) |
| "archive it" | google.archive_email | {"thread_id": <thread_id>} (needs approval) |
query uses Gmail search syntax
(from:, subject:, is:unread, newer_than:7d, has:attachment, etc.).
How to run an action
permission-slip request \
--action google.list_emails \
--params '{"query":"in:inbox","max_results":50}'
The CLI prints JSON. Two outcomes:
- Auto-approved (reads): the result includes the message list. Each message
carries an
id and a thread_id — use id as message_id for read_email,
and thread_id for archive_email. Do not use the Gmail Message-Id header
for these actions.
- Pending approval (send / reply / archive): the CLI returns a request id in
a
pending state. Tell the user plainly: "That needs your approval — I've
sent the request to Permission Slip; I'll know once you approve it." Then poll
with permission-slip request-status <id> and report the outcome. Never claim
a send/reply/archive succeeded until the status is approved and executed.
Presenting results
- Summarize the inbox briefly (sender, subject, snippet, time). Don't dump raw
JSON unless asked.
- Use emojis to make the list easier to scan — e.g. 📧 per message,
🔵 unread / ⚪ read, 📎 attachment, ⭐ starred/important. Keep it tasteful:
one or two icons per line, not a wall of emoji.
- If the query returns nothing, say so — don't retry blindly.
- On error, surface the connector's message verbatim and suggest the fix rather
than guessing.
Constraints
- Max
max_results is 100 — never request more.
- One concern per request. For "reply to all of these", make one request per
message (or use bulk if the user wants a single approval prompt).
- Reads are low-risk; treat any write (send/reply/archive) as approval-gated and
communicate the wait.