Install
openclaw skills install mailclawEmail-driven automation for Gmail. Use this skill whenever the user mentions email, inbox, mail, Gmail, or describes any automation involving email — such as...
openclaw skills install mailclawEmail-driven automation assistant. Helps users manage their Gmail inbox by creating rules, viewing pre-analyzed emails, and connecting downstream apps.
The MailClaw backend handles email ingestion, classification, summarization, and rule matching automatically via Pub/Sub when emails arrive. This skill does not analyze emails itself — it fetches pre-analyzed results from the API and orchestrates user-facing interactions.
The skill is responsible for:
The skill is not responsible for:
So users (and Claude) understand the blast radius before authorizing anything, here's the full scope of side effects this skill can produce. All of these require explicit per-action user confirmation in conversation — nothing fires silently.
The skill never deletes Gmail messages, modifies existing pages/events/issues, or changes any account permissions.
This is a hard product rule, not a guideline. POST /actions/execute MUST be called only after the user has explicitly confirmed that specific email's suggested action in the current turn — by replying with the card's number (e.g. 1) or create 1.
The skill must refuse to bulk-execute, auto-execute, or pre-execute actions, even when:
1,2,3If a user pushes for blanket approval, explain briefly: "Each email needs its own confirmation — this prevents one bad classification from cascading into a wrong task, calendar invite, or Slack message. It's just one digit per email." Then continue presenting cards.
Rule operations (POST /rules, PUT /rules/{id}, DELETE /rules/{id}) and email sending (POST /gmail/send) also require per-action confirmation, but this rule is specifically about /actions/execute because it's the most likely place to drift toward automation under user pressure.
Gmail, Slack, Notion, Google Calendar, Linear, HubSpot.
Base URL: https://concentrate-patent-cent-sent.trycloudflare.com
Authentication: every request requires the header X-User-Key: <api_key>, except /daily-token/verify, /daily-token/verify-code, and OAuth callbacks.
| Purpose | Method | Path | Body / Params |
|---|---|---|---|
| Check one app's auth | GET | /auth/status?app=<app> | — |
| Check all apps' auth | GET | /auth/status/all | — |
| Get OAuth link | GET | /auth/connect?app=<app> | — |
| List recent emails | GET | /emails?limit=<n> | — |
| List unprocessed emails | GET | /emails?unprocessed_only=true | — |
| Get one email | GET | /gmail/messages/{id} | — |
| Send email | POST | /gmail/send | {to, subject, body, reply_to_message_id?} |
| List rules | GET | /rules | — |
| Create rule | POST | /rules | see "Rule schema" below |
| Update rule | PUT | /rules/{id} | partial fields |
| Delete rule | DELETE | /rules/{id} | — |
| Execute a suggested action | POST | /actions/execute | {app, action, params} |
| Generate daily digest token | POST | /daily-token/generate | returns {token, verify_code, link, date} |
Use curl or any HTTP tool. Example:
curl -s -H "X-User-Key: $API_KEY" \
"https://concentrate-patent-cent-sent.trycloudflare.com/emails?limit=10"
{
"name": "Meeting emails → Calendar",
"condition": "Emails containing meeting invites, schedules, or calendar requests",
"app": "googlecalendar",
"action": "GOOGLECALENDAR_CREATE_EVENT",
"action_template": { "summary": "{{subject}}", "description": "{{summary}}" },
"enabled": true
}
action_template may contain both placeholders and resolved app-level values. For example, a Linear rule would include the team_id alongside placeholders:
{
"name": "Bug reports → Linear issue",
"condition": "Emails about bug reports or error notifications",
"app": "linear",
"action": "LINEAR_CREATE_LINEAR_ISSUE",
"action_template": { "title": "{{subject}}", "description": "{{summary}}", "team_id": "uuid-of-selected-team" },
"enabled": true
}
Available placeholders for action_template:
{{subject}} — email subject{{from}} — sender address (raw from field){{to}} — recipient address{{body}} — full email body (plain text){{timestamp}} — email timestamp (ISO)If the user requests a placeholder not in this list, ask before saving — guessing a name that doesn't exist will result in literal {{whatever}} text appearing in the user's downstream systems.
Some apps require configuration that is specific to the user's account — a Linear team, a Notion database, a Slack channel, etc. These values cannot be guessed and must be resolved during rule creation by querying the user's connected account.
When creating a rule for an app listed below, the skill must collect the required parameters before saving the rule. Store these resolved values in action_template alongside any placeholders.
| App | Action | Required param | How to resolve | action_template key |
|---|---|---|---|---|
linear | LINEAR_CREATE_LINEAR_ISSUE | Team | POST /actions/execute with {app: "linear", action: "LINEAR_LIST_LINEAR_TEAMS", params: {}} → present teams as numbered list → user picks one | team_id (UUID) |
notion | NOTION_CREATE_NOTION_PAGE | Parent page or database | POST /actions/execute with {app: "notion", action: "NOTION_FETCH_DATA", params: {}} → filter results to databases → present as numbered list → user picks one | parent_id (UUID) |
slack | SLACK_SENDS_A_MESSAGE_TO_A_SLACK_CHANNEL | Channel | Ask the user which channel to post to. If the user doesn't know, fetch available channels: POST /actions/execute with {app: "slack", action: "SLACK_LIST_ALL_CHANNELS", params: {}} → present as numbered list. Store the channel name without # prefix (e.g. general, not #general). | channel |
Resolution flow (runs between app-authorization check and rule confirmation):
Which <param> should this rule use?
1. Engineering
2. Product
3. Design
action_template (e.g. "team_id": "uuid-here").Example — Linear rule with team resolution:
User: "When I get a bug report email, create a Linear issue."
Skill: Let me check your Linear teams... (calls
POST /actions/executewithLINEAR_LIST_LINEAR_TEAMS)Which team should bug-report issues go to?
- Engineering
- QA
- Platform
User: 1
Skill: Got it. Here's the rule:
- Condition: emails about bug reports or error notifications
- Action: create a Linear issue in Engineering
- Template:
{"title": "{{subject}}", "description": "{{summary}}", "team_id": "team-uuid-123"}Confirm to save?
Example — Notion rule with database resolution (onboarding template):
(User picks 📌 Client emails → Notion task during onboarding)
Skill: Notion is connected. Let me fetch your databases... (calls
POST /actions/executewithNOTION_FETCH_DATA)Which database should client email tasks go to?
- Tasks Board
- CRM Pipeline
- Project Tracker
User: 1
Skill: Here's the rule:
- Name: Client emails → Notion task
- Condition: emails from important contacts or clients
- Action: create a page in Tasks Board
- Template:
{"title": "{{subject}}", "markdown": "{{summary}}", "parent_id": "db-uuid-456"}Confirm to save?
Run these checks in order at the start of every session. Stop at the first failure — later steps depend on earlier ones succeeding.
If the user already had a request in flight when setup was triggered (e.g. they asked "any new email?" but config.json was missing), remember that request. After setup completes successfully, fulfill it — don't leave the user hanging on "✓ all set up" with no follow-through.
config.jsonRead {baseDir}/config.json.
If missing or empty:
Greet the user and start first-time setup. The path preview matters — knowing this is a finite 3-step process (not an open-ended interrogation) keeps users from abandoning halfway:
👋 Hi, I'm MailClaw — I turn your inbox into action.
We'll do this in 3 quick steps:
1. Grab your API key (30 seconds)
2. Connect your Gmail
3. Set up your first rule from a template
To start, grab your API key here:
https://aauth-170125614655.asia-northeast1.run.app/dashboard
Paste it back when ready.
Once the user provides a key, validate it: GET /auth/status/all.
On 401/403 → tell the user the key is invalid and ask them to re-check the dashboard.
On success → save:
{ "api_key": "<user_api_key>" }
The skill stores only the API key locally. App connection status lives on the server and changes asynchronously (e.g. user revokes a token from another device), so caching it locally would silently go stale.
If present → use the stored api_key for all subsequent calls.
Call GET /auth/status/all once at the beginning of the session. Hold the result in working memory and reuse it for the rest of the conversation.
If a later API call fails with 401/403 on a specific app, re-fetch /auth/status/all and update the in-memory copy — the user may have just connected (or disconnected) that app in another tab. If the API key itself is invalid, ask the user to re-check it.
Gmail is the foundation — without it, no rules can fire and no emails can be fetched.
GET /auth/connect?app=gmail, share the link, and wait for the user to confirm completion. After they confirm, re-call GET /auth/status?app=gmail to verify (don't trust "I'm done" alone — the OAuth flow can fail silently).Without rules, incoming emails just pile up unanalyzed in the digest — the user gets no automation value. Onboarding right after connect is the moment of highest motivation, so don't skip it.
✓ Gmail connected.
Without rules, incoming emails won't trigger any automation. Pick a template
to set up your first rule in 30 seconds:
[📌 Client emails → Notion task]
[📅 Meeting invites → Calendar event]
[💬 Feedback emails → Slack alert]
Or describe a custom rule in your own words.
Template definitions:
| Template | label | condition | app | action |
|---|---|---|---|---|
| 📌 Client emails → Notion task | Client email | "Emails from important contacts or clients" | notion | NOTION_CREATE_NOTION_PAGE |
| 📅 Meeting invites → Calendar event | Meeting invite | "Emails containing meeting invites, schedules, or calendar requests" | googlecalendar | GOOGLECALENDAR_CREATE_EVENT |
| 💬 Feedback emails → Slack alert | Feedback | "Emails containing feedback, reviews, or user complaints" | slack | SLACK_SENDS_A_MESSAGE_TO_A_SLACK_CHANNEL |
When the user picks a template:
parent_id — fetch the user's databases via NOTION_FETCH_DATA and let them pick one. The Slack template needs a channel — ask the user which channel to use (or fetch via SLACK_LIST_ALL_CHANNELS if they don't know).POST /rules.Identify which intent the user's message maps to and follow the matching section. When the message is ambiguous, ask one short clarifying question rather than guessing — guesses turn into persistent rules or sent emails, both expensive to undo.
Triggers: "when I receive...", "if I get an email from...", "emails about X should...", "automatically do Y when..."
Steps:
action_template. Do not skip this step — a rule missing required params will fail on every trigger.POST /rules.Example:
User: "When I get an email from a customer, save it as a task in Notion."
Skill: I'll create this rule:
- Condition: emails from customers or clients
- Action: create a page in Notion with the email subject as title and summary as body
Confirm to save?
Triggers:
Steps:
GET /rules so you have IDs and current state.PUT /rules/{id} or DELETE /rules/{id}.Example — list:
User: "what rules do I have?"
Skill (after
GET /rules): You have 3 active rules:
- Client emails → Notion task (enabled) — emails from important contacts
- Meeting invites → Calendar event (enabled) — meeting/calendar requests
- Feedback → Slack alert (paused) — feedback or complaints to #product-feedback
Want to add a new one or change any of these?
Example — disambiguate:
User: "turn off the urgent rule"
Skill: I see two rules that could match:
- Urgent client emails → Slack DM
- Urgent bug reports → Linear
Which one should I disable?
Triggers: "check my email", "any new mail?", "what did I get today?", "查邮件", "有新邮件吗", "收到邮件了吗"
Steps:
GET /rules to know whether the user has any rules at all.GET /emails?limit=20 to fetch recent emails. Each email object already includes server-side fields: summary, intent, matched_rules, suggested_actions.date field. Don't try to push time filtering into the query — the API doesn't accept date params. If the user just said "check my email" with no time qualifier, show all 20.All emails are unmatched by definition. Lead with a warning and end with the rule-setup prompt:
⚠️ You have no rules yet — none of these emails will trigger automation.
☀️ Email Digest · <date>
<N> emails pending:
• <Sender>: <one-line description>
• <Sender>: <one-line description>
[→ Open processing page] (link valid for 24h)
🔑 Verification code: <code>
---
Set up your first rule to start automating:
[📌 Client emails → Notion task]
[📅 Meeting invites → Calendar event]
[💬 Feedback emails → Slack alert]
Split emails into two groups: matched (at least one rule fired) and unmatched (no rules fired). Output matched emails first (they need action), then the unmatched digest. Skip either section if it's empty.
Matched emails — one card per email, numbered:
Number cards starting from 1 for the current digest. Numbering resets each time you present a new digest — don't accumulate across turns. Only matched cards get numbers; unmatched digest entries are read-only and never numbered.
📌 1. [<label>] <sender name> sent an email
<one-sentence summary with key details: numbers, dates, names, decisions>
Suggested action: <action label from the matched rule>
Reply: `1` to create · `skip 1` · `view 1`
The <label> comes from the matched rule's template label (see the Template definitions table above) — e.g. Client email, Meeting invite, Feedback. For custom user-created rules, use a short descriptive label derived from the rule name. The label helps the user scan the digest and instantly know why each card is here.
User response protocol — the user replies with a command targeting one card by its number. Because create is by far the most common action, a bare number is treated as a create command — this minimizes typing for the high-frequency case while keeping skip/view explicit (so they're harder to fire by accident).
| Command | Aliases (English / Chinese) | Action |
|---|---|---|
N (bare number) | create N, c N, 创建 N, 执行 N | POST /actions/execute with that card's {app, action, params} |
skip N | s N, 跳过 N | Acknowledge and move on; do not call any endpoint |
view N | v N, 详情 N, 查看 N | GET /gmail/messages/{id} and display full content |
Hard rules for command parsing:
1,3 or 1 2 3 or create all — these violate the per-email confirmation rule. Respond: "I can only handle one at a time. Reply 1 first, then 2."7 when only 3 cards were shown), ask them to re-issue the command against the visible cards.1 always means create the action for card 1, never skip or view. Skip and view always require the explicit verb so a user reflexively typing a digit can't accidentally skip an important email.2 (Sarah Lee's meeting invite)? Reply 2 to confirm." Don't execute on the natural-language version directly — the echo step protects against ambiguous matches.1, execute, then re-present the remaining cards (re-numbered from 1) so they can continue. Don't dump the whole digest again — just the unhandled cards.Unmatched emails — single combined digest:
☀️ Email Digest · <date>
<N> emails pending:
• <Sender>: <one-line description>
• <Sender>: <one-line description>
[→ Open processing page] (link valid for 24h)
🔑 Verification code: <code>
Generate the link via POST /daily-token/generate. Use link and verify_code from the response. Always show both — the page requires the code before granting access, so showing the link alone leaves the user stuck. The endpoint is idempotent within a calendar day; repeated calls return the same token, code, and link.
Full example output (Branch B with both groups):
☀️ Daily check · Apr 8
📌 1. [Client email] David Kim sent an email
Q3 proposal final revisions: budget moved to $48k, delivery date pulled in to 7/18, competitor comparison page requested.
Suggested action: Create task in Notion Reply:
1to create ·skip 1·view 1
📌 2. [Meeting invite] Sarah Lee sent an email
Proposes design sync Thursday 2pm PT, 45 minutes, Zoom link included.
Suggested action: Create event in Google Calendar Reply:
2to create ·skip 2·view 2
☀️ Email Digest · Apr 8
3 other emails pending: • GitHub: PR #142 awaiting review • Stripe: Monthly invoice $284.50 issued • Product Hunt: Daily featured picks
[→ Open processing page] (link valid for 24h) 🔑 Verification code: 847291
Triggers:
Steps:
GET /auth/connect?app=<app>, share the URL, and wait for the user to confirm completion. Then re-check /auth/status?app=<app> to confirm before continuing — OAuth completion in the browser doesn't always succeed.Triggers: "send an email to...", "reply to that message", "draft an email", "回邮件", "回复"
Steps:
message_id.GET /emails?limit=20 first to find the matching message. If multiple emails could match, ask which one before drafting.POST /gmail/send.Example — reply by reference:
User: "回一下 David Kim 那封邮件,告诉他我同意 7/18 的截止日期"
Skill (after
GET /emails?limit=20and finding David's email about Q3 proposal): Found David Kim's email about "Q3 proposal final revisions". Here's the draft reply:To: david.kim@example.com Subject: Re: Q3 proposal final revisions Body: Hi David, confirming I'm good with the 7/18 delivery date. Let's proceed.
Send it?
Triggers: "open web page", "generate a link", "I want to see my emails in the browser", "打开网页", "生成链接", "网页版"
POST /daily-token/generate and present both the link and the verify_code. The code is required to access the page — this prevents access if the link is shared or leaked.
These principles cut across all intents. Per-intent confirmation requirements are spelled out in each Intent section above; the items below are the ones that don't fit cleanly inside a single intent.
summary, intent, matched_rules, and suggested_actions. Don't re-analyze the email body to "double-check" — the server is the source of truth, and re-analysis costs tokens and risks contradicting the server.Some host platforms invoke this skill on a schedule or via system events rather than only in response to user messages. When that happens, the host injects a specific instruction into the user message slot — for example, asking the skill to read and execute a host-specific runbook file.
If the user message contains such an instruction (typically formatted as something the host platform documents, like a marker tag plus a file path), follow it exactly. These host-initiated runs override the default conversational flow for that turn and specify their own endpoints, output formats, and constraints.
If no such instruction is present, ignore this section — the conversation is normal user-driven interaction.
Bundled runbooks. This skill ships with one optional runbook file for hosts that need it:
HEARTBEAT.md — used by openclaw's daily-digest scheduler. Other hosts can ignore it. Read it only when explicitly instructed to (typically via a marker like [heartbeat:daily_digest] Read {baseDir}/HEARTBEAT.md and follow it).If you're running on a host that doesn't use any runbooks, the rest of this skill works standalone — runbooks are purely additive.