OpenClawSkills
GitHub
Automation β€’ TutorialHeader.readTime

Gmail Pub/Sub

Deliver Gmail Pub/Sub to OpenClaw webhooks via gogcli.

Goal: Gmail watch β†’ Pub/Sub push β†’ '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'gog gmail watch serve'</code>' β†’ OpenClaw webhook.

Tutorial.step

Prerequisites

- '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'gcloud'</code>' installed and logged in ('<a href="https://docs.cloud.google.com/sdk/docs/install-sdk" className="text-emerald-400 hover:text-emerald-300 transition-colors">'install guide'</a>').

- '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'gog'</code>' (gogcli) installed and authorized for your Gmail account ('<a href="https://gogcli.sh/" className="text-emerald-400 hover:text-emerald-300 transition-colors">'gogcli.sh'</a>').

- OpenClaw hooks enabled (see '<a href="/automation/webhook" className="text-emerald-400 hover:text-emerald-300 transition-colors">'Webhooks'</a>').

- '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'tailscale'</code>' logged in ('<a href="https://tailscale.com/" className="text-emerald-400 hover:text-emerald-300 transition-colors">'tailscale.com'</a>'). Supported setup uses Tailscale Funnel as public HTTPS endpoint.

Other tunnel services work but are DIY/unsupported and require manual wiring.

Currently, we support Tailscale.

Example hook configuration (enables Gmail preset mapping):

Json5
{
  hooks: {
    enabled: true,
    token: "OPENCLAW_HOOK_TOKEN",
    path: "/hooks",
    presets: ["gmail"],
  },
}

To deliver Gmail summaries to chat, use mapping to override preset

Set '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'deliver'</code>' + optional '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'channel'</code>'/'<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'to'</code>':

Json5
{
  hooks: {
    enabled: true,
    token: "OPENCLAW_HOOK_TOKEN",
    presets: ["gmail"],
    mappings: [
      {
        match: { path: "gmail" },
        action: "agent",
        wakeMode: "now",
        name: "Gmail",
        sessionKey: "hook:gmail:{{messages[0].id}}",
        messageTemplate: "New email from {{messages[0].from}}
Subject: {{messages[0].subject}}
{{messages[0].snippet}}
{{messages[0].body}}",
        model: "openai/gpt-5.2-mini",
        deliver: true,
        channel: "last",
        // to: "+15551234567"
      },
    ],
  },
}

If you want a fixed channel, set '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'channel'</code>' + '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'to'</code>'. Otherwise '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'channel: &quot;last&quot;'</code>'

uses the last delivery route (falls back to WhatsApp).

To force Gmail to run a cheaper model, set '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'model'</code>' in mapping

('<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'provider/model'</code>' or alias). If you enforce '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'agents.defaults.models'</code>', include it there.

To set default model and thinking level specifically for Gmail hooks, add

'<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail.model'</code>' / '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail.thinking'</code>' in config:

Json5
{
  hooks: {
    gmail: {
      model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
      thinking: "off",
    },
  },
}

Notes:

- Per-hook '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'model'</code>'/'<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'thinking'</code>' in mapping still overrides these defaults.

- Fallback order: '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail.model'</code>' β†’ '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'agents.defaults.model.fallbacks'</code>' β†’ primary (auth/rate limit/timeout).

- If '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'agents.defaults.models'</code>' is set, Gmail model must be in allowlist.

- By default, Gmail hook content is wrapped with external content safety boundary.

To disable (dangerous), set '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail.allowUnsafeExternalContent: true'</code>'.

To further customize payload handling, add '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.mappings'</code>' or JS/TS transform modules

under '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.transformsDir'</code>' (see '<a href="/automation/webhook" className="text-emerald-400 hover:text-emerald-300 transition-colors">'Webhooks'</a>').

Tutorial.step

Wizard (recommended)

Use OpenClaw assistant to wire everything together (installs deps via brew on macOS):

Bash
openclaw webhooks gmail setup   --account [email protected]

Defaults:

- Uses Tailscale Funnel as public push endpoint.

- Writes '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail'</code>' config for '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'openclaw webhooks gmail run'</code>'.

- Enables Gmail hook preset ('<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.presets: [&quot;gmail&quot;]'</code>').

Path notes: When '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'tailscale.mode'</code>' is enabled, OpenClaw automatically sets

'<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail.serve.path'</code>' to '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'/'</code>' and keeps public path at

'<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail.tailscale.path'</code>' (default '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'/gmail-pubsub'</code>') because Tailscale

strips set-path prefix before proxy.

If backend needs to receive prefix path, set

'<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail.tailscale.target'</code>' (or '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'--tailscale-target'</code>') to full URL, e.g.

'<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'http://127.0.0.1:8788/gmail-pubsub'</code>' and match '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail.serve.path'</code>'.

Want custom endpoint? Use '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'--push-endpoint &lt;url&gt;'</code>' or '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'--tailscale off'</code>'.

Platform notes: On macOS, wizard installs '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'gcloud'</code>', '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'gogcli'</code>' and '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'tailscale'</code>'

via homebrew; on Linux install them manually first.

Gateway auto-start (recommended):

- When '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.enabled=true'</code>' and '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'hooks.gmail.account'</code>' are set, gateway starts

'<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'gog gmail watch serve'</code>' and auto-updates watch.

- Set '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'OPENCLAW_SKIP_GMAIL_WATCHER=1'</code>' to opt-out (useful if you run daemon yourself).

- Don't run manual daemon concurrently or you'll get

'<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'listen tcp 127.0.0.1:8788: bind: address already in use'</code>'.

Manual daemon (start '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'gog gmail watch serve'</code>' + auto-renewal):

Bash
openclaw webhooks gmail run
Tutorial.step

One-time setup

1. Select GCP project that owns the OAuth client used by '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'gog'</code>'.

Bash
gcloud auth login
gcloud config set project <project-id>

Note: Gmail watch requires Pub/Sub topic to be in same project as OAuth client.

2. Enable APIs:

Bash
gcloud services enable gmail.googleapis.com pubsub.googleapis.com

3. Create topic:

Bash
gcloud pubsub topics create gog-gmail-watch

4. Allow Gmail push to publish:

Bash
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch   --member=serviceAccount:[email protected]   --role=roles/pubsub.publisher
Tutorial.step

Start watch

Bash
gog gmail watch start   --account [email protected]   --label INBOX   --topic projects/<project-id>/topics/gog-gmail-watch

Save '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'history_id'</code>' from output (for debugging).

Tutorial.step

Run push handler

Local example (shared token auth):

Bash
gog gmail watch serve   --account [email protected]   --bind 127.0.0.1   --port 8788   --path /gmail-pubsub   --token <shared>   --hook-url http://127.0.0.1:18789/hooks/gmail   --hook-token OPENCLAW_HOOK_TOKEN   --include-body   --max-bytes 20000

Notes:

- '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'--token'</code>' protects push endpoint ('<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'x-gog-token'</code>' or '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'?token='</code>').

- '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'--hook-url'</code>' points to OpenClaw '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'/hooks/gmail'</code>' (mapping; isolated run + summary to main).

- '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'--include-body'</code>' and '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'--max-bytes'</code>' control body snippet sent to OpenClaw.

Recommended: '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'openclaw webhooks gmail run'</code>' wraps same flow and auto-updates watch.

Tutorial.step

Expose handler (advanced, unsupported)

If you need non-Tailscale tunnel, wire manually and use public URL in push

subscription (unsupported, no guardrails):

Bash
cloudflared tunnel --url http://127.0.0.1:8788 --no-autoupdate

Use generated URL as push endpoint:

Bash
gcloud pubsub subscriptions create gog-gmail-watch-push   --topic gog-gmail-watch   --push-endpoint "https://<public-url>/gmail-pubsub?token=<shared>"

Production: Use stable HTTPS endpoint and configure Pub/Sub OIDC JWT, then run:

Bash
gog gmail watch serve --verify-oidc --oidc-email <svc@...>
Tutorial.step

Test

Send message to watched inbox:

Bash
gog gmail send   --account [email protected]   --to [email protected]   --subject "watch test"   --body "ping"

Check watch status and history:

Bash
gog gmail watch status --account [email protected]
gog gmail history --account [email protected] --since <historyId>
Tutorial.step

Troubleshooting

- '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'Invalid topicName'</code>': project mismatch (topic not in OAuth client project).

- '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'User not authorized'</code>': topic missing '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'roles/pubsub.publisher'</code>'.

- Empty messages: Gmail push only provides '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'historyId'</code>'; fetch via '<code className="bg-white/10 px-1.5 py-0.5 rounded text-emerald-300 text-sm">'gog gmail history'</code>'.

Tutorial.step

Cleanup

Bash
gog gmail watch stop --account [email protected]
gcloud pubsub subscriptions delete gog-gmail-watch-push
gcloud pubsub topics delete gog-gmail-watch