Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/network-policy/integration-policy-examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,89 @@ Then verify the sandbox status:
$$nemoclaw my-assistant status
```

## Gmail

Apply the `gmail` preset when the sandbox needs to read or send email through the Gmail API.
The preset uses the Gmail REST API over OAuth2 and does not open SMTP or IMAP ports.

### Prerequisites

- A Google Cloud project with the Gmail API enabled.
- An OAuth2 client ID and client secret, or a service account with domain-wide delegation.
- A `credentials.json` file downloaded from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials).


### Apply the Preset

```bash
$$nemoclaw my-assistant policy-add gmail --dry-run
$$nemoclaw my-assistant policy-add gmail --yes
```

### Upload Credentials to the Sandbox

Upload your credentials file before running any Gmail script:

```bash
openshell sandbox upload /path/to/credentials.json /sandbox/credentials.json
```

For App Password authentication, upload a config file instead:

```bash
openshell sandbox upload /path/to/gmail_config.json /sandbox/gmail_config.json
```

A minimal `gmail_config.json` for App Password access:

```json
{
"email": "your-address@gmail.com",
"app_password": "xxxx xxxx xxxx xxxx"
}
```

### Read and Send Email

Connect to the sandbox and run your Gmail script:

```bash
$$nemoclaw my-assistant connect
```

From inside the sandbox, install the Google API client library if needed:

```bash
pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib
```

A minimal script to list inbox messages using the Gmail API:

```python
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow

SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]

flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
creds = flow.run_local_server(port=0)
service = build("gmail", "v1", credentials=creds)

results = service.users().messages().list(userId="me", maxResults=10).execute()
for msg in results.get("messages", []):
print(msg["id"])
```


### Remove the Preset

Remove Gmail access when the task is complete:

```bash
$$nemoclaw my-assistant policy-remove gmail --yes
```

## Inspect or Replace the Live Policy

Use `policy-list` for normal preset state:
Expand Down
39 changes: 39 additions & 0 deletions nemoclaw-blueprint/policies/presets/gmail.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

preset:
name: gmail
description: "Gmail API access via Google OAuth2"

network_policies:
gmail_api:
name: gmail_api
endpoints:
- host: gmail.googleapis.com
port: 443
protocol: rest
enforcement: enforce
rules:
- allow: { method: GET, path: "/**" }
- allow: { method: POST, path: "/**" }
- allow: { method: PUT, path: "/**" }
- allow: { method: DELETE, path: "/**" }
- allow: { method: PATCH, path: "/**" }
- host: oauth2.googleapis.com
port: 443
protocol: rest
enforcement: enforce
rules:
- allow: { method: GET, path: "/**" }
- allow: { method: POST, path: "/**" }
- host: accounts.google.com
port: 443
protocol: rest
enforcement: enforce
rules:
- allow: { method: GET, path: "/**" }
- allow: { method: POST, path: "/**" }
binaries:
- { path: /usr/local/bin/node }
- { path: /usr/bin/python3 }
- { path: /usr/local/bin/python3 }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
6 changes: 3 additions & 3 deletions test/policies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ describe("policies", () => {
"claude-code",
"discord",
"github",
"gmail",
"huggingface",
"jira",
"local-inference",
Expand Down Expand Up @@ -182,9 +183,8 @@ describe("policies", () => {
// returns 405/400 because there is no 101 Switching Protocols flow
// over h2). `access: full` + `tls: skip` keeps OpenShell out of the
// bytes so Baileys does the TLS handshake end-to-end and gets h1.
// Apex and *.web.whatsapp.com (fallback nodes w1.web.whatsapp.com,
// w2.web.whatsapp.com, ...) share the same shape so reconnects do
// not surprise the operator.
// Apex and *.web.whatsapp.com (fallback nodes w1/w2.web.whatsapp.com)
// share the same shape so reconnects do not surprise the operator.
const parsed = parsePresetYaml("whatsapp");
const endpoints: Array<Record<string, unknown>> =
parsed?.network_policies?.whatsapp?.endpoints ?? [];
Expand Down
Loading