Skip to content
Guides

Triggering OpenClaw from Gmail

The Pub/Sub topic, the Gateway hook, and a mail_reader agent that treats every message as untrusted data

7 min read

You want the assistant to react when mail lands: a new invoice, a support request, a failed deploy notice. The obvious wiring, pointing Gmail at the agent you already chat with, hands any stranger who knows your address a turn on an agent that holds your shell, your files and your browser. OpenClaw's Gmail path exists to avoid that, and it is a chain of parts rather than a switch.

How a Gmail message reaches an agent

  • Google Pub/Sub is the transport. A Gmail watch on a label publishes inbox events to a topic, Pub/Sub pushes them to the gog gmail watch serve process, and that watcher forwards the email data to the Gateway HTTP hook. It never loads an internal HOOK.md handler: this is the inbound external hook, not the in-process event system.
  • The prerequisites are specific: the gcloud CLI, gog authorised for the watched Gmail account, hooks enabled in OpenClaw, an HTTPS push endpoint that Pub/Sub can actually reach (Tailscale Funnel in the recommended setup), and a working sandbox backend, whose image you build before the first run if you stay on the default Docker backend.
  • The Gateway starts and renews the watcher itself once hooks are enabled and a Gmail account is set, with an environment variable to opt out. Run exactly one: a second serve process or a manual run command on the same listener is a bind conflict, and a listener that started is no proof that the watch registered.
  • One pushed batch becomes one agent run per email, since the mapping fans out over the messages array, capped at 200 items. Gmail paths get a larger request body allowance derived from the per-message byte limit and capped at 32 MiB, and an upstream history page counts history records, not emails, so a backlog can still hit limits.
The built-in Gmail preset's per-message session separates conversation context; it does not restrict the target agent's tools or workspace.

Per-message sessions are not a permission boundary

The preset gives each message its own session key, so the second email cannot read the first one's conversation. That is context isolation, and that is all it is. If no mapping sets an agent id, the preset resolves to your default agent, which means a stranger's email is summarised by the agent that owns your workspace and your tools. A custom mapping matching the gmail path runs before the preset, which is where you name the reader, and the hook's list of allowed agent ids stops that endpoint selecting anything else. Routing Gmail to a more capable agent is allowed, but the docs ask you to treat it as a security decision: wrapping on, run sandboxed, nothing granted beyond what the workflow needs.

The mail_reader agent

  • The reader is a second roster entry with its own workspace and model, and a sandbox set to mode all, session scope, and no workspace access. Each email therefore gets a fresh sandbox that cannot reach the host agent's workspace, and the run is disposable by construction.
  • Its tool policy is the minimal profile with one allowed tool, session_status, plus an explicit deny list covering the filesystem, runtime and web groups and the browser, cron, gateway and nodes tools. That per-agent allowlist is an absolute clamp, so a global also-allow addition cannot leak into the reader, and the explicit denies keep the intended boundary auditable.
  • Adding the reader turns the roster into an explicit fleet, so every channel your main agent still owns needs its own binding, one per channel, because there is no cross-channel wildcard. Listing agents with their bindings before you restart is the check that catches an ownerless channel, and delivery is off, so completions are logged instead of announced.

One rule catches people out: tool policies only narrow as the global, provider, agent and sandbox layers combine, so the reader's allowlist cannot restore session_status if an earlier layer removed it, and an empty effective tool set aborts the run before the model sees the email. Prove the reader before connecting mail: authenticate its provider, probe the model with the reader selected, then send a one-line turn and require the exact answer back, the only check that exercises model, runtime, sandbox and tool policy together. OpenClaw sandboxing explained covers the isolation this reader leans on, and OpenClaw multi-agent fleets explains the roster and binding rules you just changed.

Two tokens, the gcloud setup, and the boundary test

The Gmail setup command writes the transport block, enables the preset, preserves the restricted mapping you applied first, and defaults to Tailscale Funnel for the push endpoint; serve mode is tailnet-only and is not a publicly reachable Pub/Sub target, so an externally managed URL goes in with the tunnel off and the endpoint passed explicitly. Two tokens guard two hops: the push token authenticates Pub/Sub to the watcher, the hook token authenticates the watcher to OpenClaw through a header, and query-string tokens are rejected, so that token-bearing push URL is not a template for calling the hook path yourself. Setup output can print both tokens, so redact it before sharing. The manual route is short: select the project owning the gog OAuth client, enable the Gmail and Pub/Sub APIs, create the topic, grant the Gmail push service account the publisher role, then start the watch against that topic, which registers no push subscription and starts no listener. Then test. Mail yourself an inert instruction from another account, the docs suggest telling the agent to follow a link and run a command, and read the run. A watcher success acknowledges transport, a hook 200 with a run id records admission, and any attempted link navigation, file write, shell command, browser action or MCP registration is a failed boundary check. OpenClaw prompt injection is the threat model behind all of this, and the IMAP email trigger is the same reader pattern off Google.

On Diali

Diali hosts OpenClaw as a managed service, and every customer runs their own assistant rather than a seat on a shared one. The runtime configuration is generated from your dashboard settings and written again at each release, while sessions, memory and workspace files live on a persistent volume, with daily snapshots and one-click restore available through the Backups add-on (included on Max). A publicly reachable push endpoint is the piece to arrange with us, since the ingress in front of your assistant is ours. Hosted OpenClaw on Diali describes the hosted assistant, and Diali pricing lists what each plan includes.

  • The preset separates context, never permissions.
  • Two tokens, two hops, no substitutions.
  • A hook 200 is admission, not a summary.
Get started

Stop reading about it, build one

Set up an agent, pick a channel, and have it working inside the app you already keep open.