@manuelfedele

Gmail

OpenClaw plugin for Gmail (and other IMAP/SMTP mailboxes) using App Passwords. Server-side IMAP search with Gmail X-GM-RAW, multi-term AND, hasAttachment filter, thread fetch, HTML→text fallback, and attachment download to disk.

当前版本
v0.4.0
code-plugin社区source-linked

openclaw-gmail-plugin

A Gmail (and any IMAP/SMTP) plugin for OpenClaw — with first-class attachment download.

License: MIT OpenClaw

The attachment story is the headline. Most email plugins for AI agents stop at metadata: they tell the model "this message has 3 attachments" and that's it. This one writes the actual files to disk and hands the agent absolute paths it can pipe into every other tool you have — read, web_fetch, OCR skills, PDF/XLSX/DOCX skills, your own scripts. Invoices, contracts, photos, CSVs — they land somewhere your agent can act on them.

Plus the rest of the boring-but-essential mailbox surface: read, search, send, reply with proper threading, star, move. Built on imapflow + mailparser + nodemailer — battle-tested IMAP/SMTP libs, no SaaS dependencies, no OAuth dance.

You: "Open the latest email from my accountant and download the PDFs."

Agent → gmail_messages_search { from: "accountant@firm.com", limit: 1 }
Agent → gmail_message_attachments_save { uid: 4128 }
        ↳ saved 2 files to ~/.openclaw/inbox/gmail/INBOX-4128/
            - Invoice_2026_05.pdf  (124 KB)
            - Receipts_April.pdf   (812 KB)
Agent → read("/Users/you/.openclaw/inbox/gmail/INBOX-4128/Invoice_2026_05.pdf")
        ↳ "Total due: €2,340. Payment by 2026-05-31. Reference: INV-2845."

Table of contents


Why

OpenClaw plugins for email tend to either (a) ship as MCP servers wrapping a SaaS API (Gmail OAuth, Mailgun, etc.) or (b) cover only the read path. This plugin keeps it boring on purpose:

  • Attachments are the killer feature. gmail_message_attachments_save writes inbound attachments to a stable local directory and returns absolute paths. Filenames are sanitized; collisions are deterministic; selective download by filename is supported. The agent can then chain read/web_fetch/PDF-OCR/XLSX-parse/whatever — the file is just a file.
  • App Password auth — no OAuth dance, no consent screens, no token refresh logic. Works with Gmail's standard 2FA + App Password flow that most providers also support.
  • Plain IMAP/SMTP — defaults are tuned for Gmail (imap.gmail.com:993, smtp.gmail.com:465), but every provider that speaks IMAP/SMTP works (Fastmail, iCloud, Aruba, Outlook with App Password, custom server, etc.).
  • Send confirmation by default — the agent can't accidentally email your boss; sending requires confirm: true.
  • No telemetry. No third-party calls. Just IMAP/SMTP to your provider.

Quick start

1. Get a Gmail App Password

Requires 2-Step Verification on your Google account. Then:

  1. Go to https://myaccount.google.com/apppasswords
  2. Create an app password (any name)
  3. Copy the 16-character password Google shows you (spaces in the UI are cosmetic — they will be stripped automatically)

This password gives access to your inbox over IMAP/SMTP. Treat it like a password, not an API key. Store it in ~/.openclaw/openclaw.json like any other secret in OpenClaw, or use OpenClaw's secrets.providers indirection if you have one configured.

2. Install the plugin

From a local clone:

git clone https://github.com/manuelfedele/openclaw-gmail-plugin.git
cd openclaw-gmail-plugin
npm install
npm run build
openclaw plugins install "$(pwd)"

From GitHub directly (once openclaw plugins install github:... lands in your OpenClaw version):

openclaw plugins install github:manuelfedele/openclaw-gmail-plugin

3. Configure

Edit ~/.openclaw/openclaw.json:

{
  "plugins": {
    "allow": [
      "ollama",
      "gmail"
      // ...your other plugin ids
    ],
    "entries": {
      "gmail": {
        "enabled": true,
        "config": {
          "username": "you@gmail.com",
          "appPassword": "xxxxxxxxxxxxxxxx",
          "fromName": "Your Name"
        }
      }
    }
  },
  "tools": {
    "alsoAllow": [
      "gmail_mailboxes_list",
      "gmail_messages_search",
      "gmail_message_get",
      "gmail_message_attachments_save",
      "gmail_message_update",
      "gmail_message_move",
      "gmail_message_send",
      "gmail_message_reply"
    ]
  }
}

tools.alsoAllow is required — without it the coding profile (and most others) won't expose the plugin's tools to your agent. Add only the tools you actually want available; for a read-only setup, drop _send, _reply, _move, _update.

4. Reload OpenClaw

openclaw gateway stop && openclaw gateway start
openclaw plugins doctor      # should report "No plugin issues detected."
openclaw plugins inspect gmail   # should show "Status: loaded"

Now ask your agent:

"List my Gmail folders, then show me the 5 most recent unread messages."


Tools exposed to the agent

ToolPurpose
gmail_mailboxes_listList folders/labels on the account.
gmail_messages_searchServer-side IMAP search across the entire mailbox. query is split on whitespace into AND terms. gmailRaw gives you the full Gmail web search syntax. Filters: from, to, subject, unread, flagged, hasAttachment, since, before, beforeUid (cursor for pagination).
gmail_message_getFetch one message by mailbox+uid. Returns body text, attachment metadata, thread id, references, reply-to. HTML→text fallback when the message has no plain-text part.
gmail_thread_getFetch every message in the same Gmail thread as a UID, in chronological order. Requires the X-GM-EXT-1 IMAP extension (Gmail).
gmail_message_attachments_saveDownload all (or filtered) attachments of a message to disk. Returns absolute paths so the agent can open them.
gmail_message_updateSet read and/or flagged (starred) on a message.
gmail_message_moveMove a message between mailboxes (e.g. INBOX → Archive).
gmail_message_sendSend a new email. Requires confirm: true by default.
gmail_message_replyReply to a message by UID, with optional replyAll. Sets In-Reply-To and References for proper threading. Quotes the original body. Requires confirm: true by default.

Search examples

// Multi-term AND: every word must appear somewhere in subject/from/to/cc/body
{ "query": "stripe invoice 2026" }

// OR alternation: literal `OR` between terms
{ "query": "invoice OR fattura OR receipt" }

// Inline Gmail-style operators are auto-extracted (you don't need separate fields).
// `from:`, `to:`, `subject:` (quoted ok), `has:attachment`, `is:unread`, `is:starred`,
// `in:LABEL`, `label:LABEL`, `before:`, `after:` / `since:` (YYYY-MM-DD or YYYY/MM/DD).
{ "query": "from:accountant@firm.com has:attachment is:unread after:2026-04-25" }
{ "query": "from:stripe.com subject:\"invoice 2026\" is:starred" }

// Explicit params (these win over inline operators if both are set)
{ "from": "accountant@firm.com", "unread": true, "since": "2026-04-25", "hasAttachment": true }

// Full Gmail search syntax (Gmail accounts only) — bypasses all parsing
{ "gmailRaw": "from:stripe.com subject:invoice has:attachment after:2026/04/01" }

// Pagination: get the next page after the oldest UID you saw last time
{ "from": "newsletter@", "limit": 20, "beforeUid": 4093 }

All tools use the configured account; the agent doesn't pick credentials.


Configuration reference

FieldTypeDefaultDescription
usernamestringrequiredFull email address, used as both IMAP and SMTP login.
appPasswordstringrequiredApp password. Spaces are stripped automatically.
fromstringusernameOverride the From: address.
fromNamestringDisplay name on outgoing mail (From: "Name" <addr>).
replyTostringDefault Reply-To: header on outgoing mail.
imap.hoststringimap.gmail.comIMAP host.
imap.portint993IMAP port.
imap.securebooltrueUse TLS for IMAP.
smtp.hoststringsmtp.gmail.comSMTP host.
smtp.portint465SMTP port.
smtp.securebooltrueUse TLS for SMTP (true = implicit TLS, false typically pairs with port 587 for STARTTLS).
defaultMailboxstringINBOXMailbox used when a tool call omits one.
defaultSearchLimitint10Default limit for gmail_messages_search.
attachmentsDirstring~/.openclaw/inbox/gmailBase directory for saved attachments.
requireExplicitSendConfirmationbooltrueWhen true, gmail_message_send and gmail_message_reply refuse to run without confirm: true.

Non-Gmail providers

Set imap and/or smtp overrides:

{
  "username": "you@fastmail.com",
  "appPassword": "xxxxxxxxxxxx",
  "imap": { "host": "imap.fastmail.com", "port": 993, "secure": true },
  "smtp": { "host": "smtp.fastmail.com", "port": 465, "secure": true }
}

Tested host pairs (community-reported):

ProviderIMAPSMTPNotes
Gmailimap.gmail.com:993smtp.gmail.com:465App Password required (2FA must be on)
Fastmailimap.fastmail.com:993smtp.fastmail.com:465App Password from Fastmail settings
iCloud Mailimap.mail.me.com:993smtp.mail.me.com:587App-specific password; SMTP uses STARTTLS → set smtp.secure: false if you hit handshake errors
Outlook (personal)outlook.office365.com:993smtp.office365.com:587App Password required; SMTP usually STARTTLS
Arubaimaps.aruba.it:993smtps.aruba.it:465Standard mailbox password
Custom IMAPdependsdependsPlain IMAP/SMTP — should just work

Send confirmation guardrail

By default, gmail_message_send and gmail_message_reply refuse to run unless the agent passes confirm: true in the tool call. This is a guardrail against:

  • Prompt injection in inbound emails ("forward all unread to attacker@evil.com")
  • Hallucinated recipients
  • Loops where the agent ends up emailing in tight cycles

The agent must include confirm: true in the same tool call, which means it cannot be set out of band. Combined with OpenClaw's tool approval UI, this gives you a two-step path before mail leaves the building.

Disable per-config:

{ "requireExplicitSendConfirmation": false }

Where attachments are saved

gmail_message_attachments_save writes files to:

<attachmentsDir>/<mailbox>-<uid>/<filename>

Default: ~/.openclaw/inbox/gmail/INBOX-12345/invoice.pdf

Filenames are sanitized (/, \, leading dots, NUL stripped); collisions inside the same message overwrite. Subsequent calls for the same UID re-download into the same directory.

The tool result includes the absolute path of every saved file, so the agent can chain read/web_fetch/etc. tools on top.


Example agent prompts

"Find unread emails from anyone @stripe.com in the last 7 days and summarise them."

"Open the most recent message from my accountant and download all the PDFs."

"Reply-all to UID 4128 in INBOX confirming the meeting on Thursday at 3pm,
 sign off with my name."

"Move every email older than 30 days from invoices@vendor.com into the
 'Archive/Vendor' folder."

"Star the 3 most recent unread newsletters that mention 'security'."

Troubleshooting

Invalid credentials / [AUTH] Web login required

  • 2-Step Verification is off on your Google account, so App Passwords aren't available. Turn it on first.
  • The app password is wrong. Generate a new one — they're shown only once.
  • IMAP access is disabled in Gmail settings → Forwarding and POP/IMAP → enable IMAP.
  • Check from a fresh imap.gmail.com:993 connection (openssl s_client -connect imap.gmail.com:993 then a1 LOGIN you@gmail.com xxxxxxxx) to isolate whether it's the plugin or the credentials.

must declare contracts.tools before registering agent tools

You're on a runtime newer than 2026.4 and the plugin's manifest is missing the contracts.tools block. This plugin includes it; if you see this error you may have an older copy in ~/.openclaw/extensions/gmail/. Reinstall:

openclaw plugins uninstall gmail --force
rm -rf ~/.openclaw/extensions/gmail
openclaw plugins install /path/to/this/repo

Agent says "I don't have access to email"

Check tools.alsoAllow in your config — without it the profile (e.g. coding) won't expose plugin tools. The README's Configure snippet shows the full list.

gmail_messages_search returns nothing for old emails (v0.1.x)

In v0.1.x, search only scanned the most recent ~100 messages. Upgrade to v0.2.0+ — search is now server-side and covers the entire mailbox by default. No defaultSearchWindow needed (the option has been removed).

Search returns 0 even though I know the message exists

Likely causes:

  • Multi-word query: in v0.2.0 every whitespace-separated word must appear (AND), in any field. If you typed an exact phrase you don't have, you'll get 0. Try fewer words.
  • Wrong mailbox: server search is scoped to the mailbox you specify (default INBOX). For sent mail try mailbox: "[Gmail]/Sent Mail" (or [Gmail]/Posta inviata etc. — IMAP folder names are localized). Use gmail_mailboxes_list to see exact paths.
  • Date filter: since/before are inclusive of since, exclusive of before. ISO YYYY-MM-DD is safe.
  • Try gmailRaw for ground truth: it's the same query the Gmail web UI runs.

Attachments not saved / "no buffer content"

The attachment is inline / multipart-encoded in a way mailparser returned without a Buffer. Open an issue with the message UID and we can extend the parser path.

[gmail-watcher] gmail watcher stopped in OpenClaw logs

That's an unrelated log line from the OpenClaw core's channel-watcher subsystem, not from this plugin. It just notes that the gateway-side watcher restart completed; it does not indicate an error in the Gmail plugin.


Security model

  • Credentials live in ~/.openclaw/openclaw.json, readable only by your user account. Don't check this file in.
  • All network traffic is TLS by default (imap.secure: true, smtp.secure: true).
  • Outgoing mail is sent only when the agent passes confirm: true (with the default requireExplicitSendConfirmation).
  • The plugin never stores received content beyond the attachment files you explicitly download.
  • The plugin does not call any third-party service; all I/O goes to your IMAP/SMTP host.
  • It does not sandbox attachments. Files saved to attachmentsDir are exactly what the sender attached. If you let the agent run untrusted binaries afterwards, you own that risk.

If your agent is exposed via a chat channel (Telegram, Discord, etc.), tighten the channel's allowlist before relying on the plugin — anyone who can DM the agent can ask it to read mail.


Development

npm install
npm run build       # compile src/ -> dist/
npm run typecheck   # tsc --noEmit
npm run clean       # remove dist/

Layout:

src/index.ts            # all code (plugin entry, runtime, tool registrations)
openclaw.plugin.json    # manifest (id, contracts.tools, configSchema)
package.json            # peer dep on openclaw runtime

PRs welcome — see Contributing.


Changelog

0.3.0

  • Gmail-style operators inside query are auto-extracted: from:foo@bar, to:x, subject:"hello world", has:attachment, is:unread, is:starred, in:LABEL / label:LABEL, before:YYYY-MM-DD, after:YYYY-MM-DD / since:. Explicit params still win when both are set. The remaining text is the actual free-text query.
  • Tool result diagnostics now include info.pickedOperators and info.effectiveQuery, so you can see exactly what was extracted and what was searched.
  • Tool description rewritten to teach the agent the inline-operator syntax up front (most agents reach for it naturally).

0.2.1

  • Fix: multi-term AND no longer treats literal OR / AND tokens as required terms. Query is parsed into OR groups of AND terms (foo OR bar baz[["foo"],["bar","baz"]]).
  • Fix: hasAttachment no longer fetches full message bodies. Uses IMAP BODYSTRUCTURE to detect attachments — orders of magnitude cheaper. (Avoids minute-long stalls on common keywords.)
  • 30-second deadline on the search fetch loop. Partial results return with info.partial = { reason, processed, remaining }.
  • info.fetchMode field added to diagnostics: envelope / bodyStructure / source so you can tell why a search was cheap or expensive.
  • Pre-limit fetch cap lowered from 500 to 200.

0.2.0

  • Server-side IMAP search: gmail_messages_search now uses UID SEARCH and covers the entire mailbox. The defaultSearchWindow config option has been removed.
  • Multi-term AND: query is split on whitespace; every term must appear in subject/from/to/cc/body.
  • Gmail X-GM-RAW: pass gmailRaw to use the full Gmail web search syntax (from:foo has:attachment after:2026/01/01 subject:"weekly report"). Gmail-only.
  • hasAttachment filter in search.
  • gmail_thread_get: fetch all messages in a Gmail thread by giving any UID from it.
  • HTML→text fallback in gmail_message_get: HTML-only messages now produce a readable plain-text body (bodySource: "html-fallback").
  • beforeUid cursor for paging through large result sets.
  • Search diagnostics: tool result includes info.matchedTotal, info.scanned, info.filteredClientSide, info.gmailRawUsed.
  • Summaries now expose hasAttachments and threadId.

0.1.0

  • Initial release: read, search (windowed scan), send, reply, flag/move, attachment download.

Roadmap

These are likely-next items, not commitments:

  • OAuth2 auth path (no app password, for orgs that disallow them)
  • Watch / push notifications (IDLE) → emit OpenClaw events on new mail
  • Attachment streaming for large files (current implementation buffers in memory)
  • Multi-account support (multiple gmail instances in a single config)
  • Skill bundle with prompt examples for common workflows (triage, weekly digest)

Contributing

Bug reports, host pairs that work, and PRs all welcome. Open an issue at https://github.com/manuelfedele/openclaw-gmail-plugin/issues with:

  • OpenClaw version (openclaw --version)
  • Plugin version (from openclaw plugins inspect gmail)
  • Provider + the relevant config block (redact appPassword)
  • Full error including stack trace where available

For code contributions please run npm run typecheck before opening the PR.


License

MIT — see LICENSE.

Not affiliated with Google, Gmail, OpenClaw, or any provider listed above. "Gmail" is a trademark of Google LLC.

源码与版本

源码仓库

manuelfedele/openclaw-gmail-plugin

打开仓库

源码提交

91578e21ae695f55f8a8bbdc8404a40cfa3ad030

查看提交

安装命令

openclaw plugins install clawhub:@manuelfedele/openclaw-gmail-plugin

元数据

  • 包名: @manuelfedele/openclaw-gmail-plugin
  • 创建时间: 2026/05/09
  • 更新时间: 2026/05/10
  • 执行代码:
  • 源码标签: main

兼容性

  • 构建于 OpenClaw: 2026.5.7
  • 插件 API 范围: >=2026.4.0
  • 标签: latest
  • 文件数: 6