{ "title": "Gmail Pub/Sub -> OpenClaw", "content": "Goal: Gmail watch -> Pub/Sub push -> `gog gmail watch serve` -> OpenClaw webhook.\n\n* `gcloud` installed and logged in ([install guide](https://docs.cloud.google.com/sdk/docs/install-sdk)).\n* `gog` (gogcli) installed and authorized for the Gmail account ([gogcli.sh](https://gogcli.sh/)).\n* OpenClaw hooks enabled (see [Webhooks](/automation/webhook)).\n* `tailscale` logged in ([tailscale.com](https://tailscale.com/)). Supported setup uses Tailscale Funnel for the public HTTPS endpoint.\n Other tunnel services can work, but are DIY/unsupported and require manual wiring.\n Right now, Tailscale is what we support.\n\nExample hook config (enable Gmail preset mapping):\n\nTo deliver the Gmail summary to a chat surface, override the preset with a mapping\nthat sets `deliver` + optional `channel`/`to`:\n\nIf you want a fixed channel, set `channel` + `to`. Otherwise `channel: \"last\"`\nuses the last delivery route (falls back to WhatsApp).\n\nTo force a cheaper model for Gmail runs, set `model` in the mapping\n(`provider/model` or alias). If you enforce `agents.defaults.models`, include it there.\n\nTo set a default model and thinking level specifically for Gmail hooks, add\n`hooks.gmail.model` / `hooks.gmail.thinking` in your config:\n\n* Per-hook `model`/`thinking` in the mapping still overrides these defaults.\n* Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).\n* If `agents.defaults.models` is set, the Gmail model must be in the allowlist.\n* Gmail hook content is wrapped with external-content safety boundaries by default.\n To disable (dangerous), set `hooks.gmail.allowUnsafeExternalContent: true`.\n\nTo customize payload handling further, add `hooks.mappings` or a JS/TS transform module\nunder `hooks.transformsDir` (see [Webhooks](/automation/webhook)).\n\n## Wizard (recommended)\n\nUse the OpenClaw helper to wire everything together (installs deps on macOS via brew):\n\n* Uses Tailscale Funnel for the public push endpoint.\n* Writes `hooks.gmail` config for `openclaw webhooks gmail run`.\n* Enables the Gmail hook preset (`hooks.presets: [\"gmail\"]`).\n\nPath note: when `tailscale.mode` is enabled, OpenClaw automatically sets\n`hooks.gmail.serve.path` to `/` and keeps the public path at\n`hooks.gmail.tailscale.path` (default `/gmail-pubsub`) because Tailscale\nstrips the set-path prefix before proxying.\nIf you need the backend to receive the prefixed path, set\n`hooks.gmail.tailscale.target` (or `--tailscale-target`) to a full URL like\n`http://127.0.0.1:8788/gmail-pubsub` and match `hooks.gmail.serve.path`.\n\nWant a custom endpoint? Use `--push-endpoint ` or `--tailscale off`.\n\nPlatform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale`\nvia Homebrew; on Linux install them manually first.\n\nGateway auto-start (recommended):\n\n* When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts\n `gog gmail watch serve` on boot and auto-renews the watch.\n* Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out (useful if you run the daemon yourself).\n* Do not run the manual daemon at the same time, or you will hit\n `listen tcp 127.0.0.1:8788: bind: address already in use`.\n\nManual daemon (starts `gog gmail watch serve` + auto-renew):\n\n1. Select the GCP project **that owns the OAuth client** used by `gog`.\n\nNote: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.\n\n4. Allow Gmail push to publish:\n\nSave the `history_id` from the output (for debugging).\n\n## Run the push handler\n\nLocal example (shared token auth):\n\n* `--token` protects the push endpoint (`x-gog-token` or `?token=`).\n* `--hook-url` points to OpenClaw `/hooks/gmail` (mapped; isolated run + summary to main).\n* `--include-body` and `--max-bytes` control the body snippet sent to OpenClaw.\n\nRecommended: `openclaw webhooks gmail run` wraps the same flow and auto-renews the watch.\n\n## Expose the handler (advanced, unsupported)\n\nIf you need a non-Tailscale tunnel, wire it manually and use the public URL in the push\nsubscription (unsupported, no guardrails):\n\nUse the generated URL as the push endpoint:\n\nProduction: use a stable HTTPS endpoint and configure Pub/Sub OIDC JWT, then run:\n\nSend a message to the watched inbox:\n\nCheck watch state and history:\n\n* `Invalid topicName`: project mismatch (topic not in the OAuth client project).\n* `User not authorized`: missing `roles/pubsub.publisher` on the topic.\n* Empty messages: Gmail push only provides `historyId`; fetch via `gog gmail history`.", "code_samples": [ { "code": "To deliver the Gmail summary to a chat surface, override the preset with a mapping\nthat sets `deliver` + optional `channel`/`to`:", "language": "unknown" }, { "code": "If you want a fixed channel, set `channel` + `to`. Otherwise `channel: \"last\"`\nuses the last delivery route (falls back to WhatsApp).\n\nTo force a cheaper model for Gmail runs, set `model` in the mapping\n(`provider/model` or alias). If you enforce `agents.defaults.models`, include it there.\n\nTo set a default model and thinking level specifically for Gmail hooks, add\n`hooks.gmail.model` / `hooks.gmail.thinking` in your config:", "language": "unknown" }, { "code": "Notes:\n\n* Per-hook `model`/`thinking` in the mapping still overrides these defaults.\n* Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).\n* If `agents.defaults.models` is set, the Gmail model must be in the allowlist.\n* Gmail hook content is wrapped with external-content safety boundaries by default.\n To disable (dangerous), set `hooks.gmail.allowUnsafeExternalContent: true`.\n\nTo customize payload handling further, add `hooks.mappings` or a JS/TS transform module\nunder `hooks.transformsDir` (see [Webhooks](/automation/webhook)).\n\n## Wizard (recommended)\n\nUse the OpenClaw helper to wire everything together (installs deps on macOS via brew):", "language": "unknown" }, { "code": "Defaults:\n\n* Uses Tailscale Funnel for the public push endpoint.\n* Writes `hooks.gmail` config for `openclaw webhooks gmail run`.\n* Enables the Gmail hook preset (`hooks.presets: [\"gmail\"]`).\n\nPath note: when `tailscale.mode` is enabled, OpenClaw automatically sets\n`hooks.gmail.serve.path` to `/` and keeps the public path at\n`hooks.gmail.tailscale.path` (default `/gmail-pubsub`) because Tailscale\nstrips the set-path prefix before proxying.\nIf you need the backend to receive the prefixed path, set\n`hooks.gmail.tailscale.target` (or `--tailscale-target`) to a full URL like\n`http://127.0.0.1:8788/gmail-pubsub` and match `hooks.gmail.serve.path`.\n\nWant a custom endpoint? Use `--push-endpoint ` or `--tailscale off`.\n\nPlatform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale`\nvia Homebrew; on Linux install them manually first.\n\nGateway auto-start (recommended):\n\n* When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts\n `gog gmail watch serve` on boot and auto-renews the watch.\n* Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out (useful if you run the daemon yourself).\n* Do not run the manual daemon at the same time, or you will hit\n `listen tcp 127.0.0.1:8788: bind: address already in use`.\n\nManual daemon (starts `gog gmail watch serve` + auto-renew):", "language": "unknown" }, { "code": "## One-time setup\n\n1. Select the GCP project **that owns the OAuth client** used by `gog`.", "language": "unknown" }, { "code": "Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.\n\n2. Enable APIs:", "language": "unknown" }, { "code": "3. Create a topic:", "language": "unknown" }, { "code": "4. Allow Gmail push to publish:", "language": "unknown" }, { "code": "## Start the watch", "language": "unknown" }, { "code": "Save the `history_id` from the output (for debugging).\n\n## Run the push handler\n\nLocal example (shared token auth):", "language": "unknown" }, { "code": "Notes:\n\n* `--token` protects the push endpoint (`x-gog-token` or `?token=`).\n* `--hook-url` points to OpenClaw `/hooks/gmail` (mapped; isolated run + summary to main).\n* `--include-body` and `--max-bytes` control the body snippet sent to OpenClaw.\n\nRecommended: `openclaw webhooks gmail run` wraps the same flow and auto-renews the watch.\n\n## Expose the handler (advanced, unsupported)\n\nIf you need a non-Tailscale tunnel, wire it manually and use the public URL in the push\nsubscription (unsupported, no guardrails):", "language": "unknown" }, { "code": "Use the generated URL as the push endpoint:", "language": "unknown" }, { "code": "Production: use a stable HTTPS endpoint and configure Pub/Sub OIDC JWT, then run:", "language": "unknown" }, { "code": "## Test\n\nSend a message to the watched inbox:", "language": "unknown" }, { "code": "Check watch state and history:", "language": "unknown" }, { "code": "## Troubleshooting\n\n* `Invalid topicName`: project mismatch (topic not in the OAuth client project).\n* `User not authorized`: missing `roles/pubsub.publisher` on the topic.\n* Empty messages: Gmail push only provides `historyId`; fetch via `gog gmail history`.\n\n## Cleanup", "language": "unknown" } ], "headings": [ { "level": "h2", "text": "Prereqs", "id": "prereqs" }, { "level": "h2", "text": "Wizard (recommended)", "id": "wizard-(recommended)" }, { "level": "h2", "text": "One-time setup", "id": "one-time-setup" }, { "level": "h2", "text": "Start the watch", "id": "start-the-watch" }, { "level": "h2", "text": "Run the push handler", "id": "run-the-push-handler" }, { "level": "h2", "text": "Expose the handler (advanced, unsupported)", "id": "expose-the-handler-(advanced,-unsupported)" }, { "level": "h2", "text": "Test", "id": "test" }, { "level": "h2", "text": "Troubleshooting", "id": "troubleshooting" }, { "level": "h2", "text": "Cleanup", "id": "cleanup" } ], "url": "llms-txt#gmail-pub/sub-->-openclaw", "links": [] }