> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Gmail

The io.b3os/gmail pack: 6 actions with call snippets, fixture outputs, connection setup and error codes, generated from version 1.0.0.

`io.b3os/gmail` 1.0.0 · `email`, `productivity` · MIT

Search and read mail, send plain-text messages, create drafts and change labels (read, starred, archived) in a Gmail mailbox with your own Google OAuth token. Reading gives headers, labels and a snippet; full bodies arrive base64-encoded from Gmail, which declarative actions cannot decode, so they are not included. Needs gmail.readonly, gmail.send, gmail.compose or gmail.modify depending on the actions you use.

Every call's result carries `upstream_status` and `timing`: `validate_ms`, `inject_ms`, `render_ms`, `upstream_ms`, `map_ms` and `total_ms`. `upstream` is the service's time; the others are B3OS's own ([call actions](/actions/call)). The snippets below show no timing values, because a fixture records none.

## Connection

`google`, type `oauth2`: Google account (Gmail).

An OAuth access token for your Google account from your own Google Cloud OAuth client, with the scopes listed here. Store the refresh_token, client_id and client_secret with it so the vault can refresh the token (access tokens last an hour); B3OS never uses a Google client of its own.

How to get one: [developers.google.com](https://developers.google.com/identity/protocols/oauth2/web-server).

Scopes the pack's actions use: `https://www.googleapis.com/auth/gmail.readonly`, `https://www.googleapis.com/auth/gmail.send`, `https://www.googleapis.com/auth/gmail.compose`, `https://www.googleapis.com/auth/gmail.modify`. The manifest names them per connection, not per action.

The credential is sent only to `gmail.googleapis.com`.

Store the credential once as a [vault connection](/vault) for this pack and key, then name its id in a call's `connections`. Leave the key out and B3OS uses the project's newest connection for the pack and key without a `subject` ([connections](/actions/call#connections)).

Hosts it reaches: `gmail.googleapis.com`.

## Actions

| Action | Effect | MCP tool | Title |
|---|---|---|---|
| [`list-messages`](/actions/catalog/gmail#gmail-list-messages) | read | `gmail__list-messages` | Search messages |
| [`get-message`](/actions/catalog/gmail#gmail-get-message) | read | `gmail__get-message` | Get message |
| [`send-email`](/actions/catalog/gmail#gmail-send-email) | write | `gmail__send-email` | Send email |
| [`create-draft`](/actions/catalog/gmail#gmail-create-draft) | write | `gmail__create-draft` | Create draft |
| [`modify-labels`](/actions/catalog/gmail#gmail-modify-labels) | write | `gmail__modify-labels` | Change message labels |
| [`list-labels`](/actions/catalog/gmail#gmail-list-labels) | read | `gmail__list-labels` | List labels |

list-messages

**Search messages.** Message and thread ids matching a Gmail search (the same syntax as the search box: from:alice is:unread newer_than:7d has:attachment label:work), newest first. Read each with get-message. Page with page_token.

Ref `io.b3os/gmail/list-messages@1.0.0` · effect `read` · connection `google` · MCP tool `gmail__list-messages`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/gmail/list-messages@1",
          "input": {
            "query": "from:releases@example.com newer_than:7d",
            "max_results": 2
          },
          "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/gmail/list-messages@1",
      input: { query: "from:releases@example.com newer_than:7d", max_results: 2 },
      connections: { google: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/gmail/list-messages@1",
        "input": {
            "query": "from:releases@example.com newer_than:7d",
            "max_results": 2,
        },
        "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "gmail__list-messages",
        "arguments": {
          "query": "from:releases@example.com newer_than:7d",
          "max_results": 2
        }
      }
    }
    ```

    Expose the pack as tools with `--packs gmail` on the local server or `?packs=gmail` on the remote URL, and the agent calls `gmail__list-messages` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Search messages (documented response shape, example values). From the bundle's `fixtures/list-messages.json`, not a live call.

```json
{
  "messages": [
    {"id": "18c9a2b3d4e5f6a7", "thread_id": "18c9a2b3d4e5f6a7"},
    {"id": "18c99f0e1d2c3b4a", "thread_id": "18c99f0e1d2c3b4a"}
  ],
  "next_page_token": "09876543210987654321",
  "result_size_estimate": 12
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | built from the service's response |
| `connection_unauthorized` | 401 | no | Google rejected the access token (expired or revoked); refresh or reconnect it. |
| `rate_limited` | 429, 403 | yes | built from the service's response |
| `forbidden` | 403 | no | built from the service's response |
| `invalid_request` | 400 | no | built from the service's response |
| `conflict` | 409 | no | built from the service's response |

get-message

**Get message.** One message's From, To, Cc, Subject, Date and Message-ID headers, labels, a text snippet, size and receive time. The body is not included (see the pack description).

Ref `io.b3os/gmail/get-message@1.0.0` · effect `read` · connection `google` · MCP tool `gmail__get-message`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/gmail/get-message@1",
          "input": {"id": "18c9a2b3d4e5f6a7"},
          "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/gmail/get-message@1",
      input: { id: "18c9a2b3d4e5f6a7" },
      connections: { google: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/gmail/get-message@1",
        "input": {"id": "18c9a2b3d4e5f6a7"},
        "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "gmail__get-message",
        "arguments": {"id": "18c9a2b3d4e5f6a7"}
      }
    }
    ```

    Expose the pack as tools with `--packs gmail` on the local server or `?packs=gmail` on the remote URL, and the agent calls `gmail__get-message` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Get message (documented response shape, example values). From the bundle's `fixtures/get-message.json`, not a live call.

```json
{
  "id": "18c9a2b3d4e5f6a7",
  "thread_id": "18c9a2b3d4e5f6a7",
  "labels": ["UNREAD", "IMPORTANT", "CATEGORY_PERSONAL", "INBOX"],
  "snippet": "The release is live; the changelog has the details.",
  "from": "Releases <releases@example.com>",
  "to": "mona@acme.example",
  "cc": null,
  "subject": "Release v2.4.0",
  "date": "Mon, 26 Oct 2026 07:56:40 +0000",
  "message_id": "<CAB3OSexample123@mail.gmail.com>",
  "internal_date_ms": "1792999000000",
  "size_estimate": 5012
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | built from the service's response |
| `connection_unauthorized` | 401 | no | Google rejected the access token (expired or revoked); refresh or reconnect it. |
| `rate_limited` | 429, 403 | yes | built from the service's response |
| `forbidden` | 403 | no | built from the service's response |
| `invalid_request` | 400 | no | built from the service's response |
| `conflict` | 409 | no | built from the service's response |

send-email

**Send email.** Send a plain-text email from the connected mailbox to one or more recipients, with optional Cc and Bcc. Returns the sent message id and thread id. Not idempotent: two calls send two emails.

Ref `io.b3os/gmail/send-email@1.0.0` · effect `write` · connection `google` · MCP tool `gmail__send-email`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Idempotency-Key: 7c1e2f6a-1d43-4a8e-b2a5-3f0c9d5e8b11" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/gmail/send-email@1",
          "input": {
            "to": "ops@example.com",
            "cc": "mona@acme.example",
            "subject": "Deploy finished",
            "text": "v2.4.0 is live.\r\nRollback plan: redeploy v2.3.9."
          },
          "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/gmail/send-email@1",
      input: {
        to: "ops@example.com",
        cc: "mona@acme.example",
        subject: "Deploy finished",
        text: "v2.4.0 is live.\r\nRollback plan: redeploy v2.3.9.",
      },
      connections: { google: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    }, { idempotencyKey: crypto.randomUUID() });
    console.log(result.output);
    ```
  **Python**

```python
    import uuid
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/gmail/send-email@1",
        "input": {
            "to": "ops@example.com",
            "cc": "mona@acme.example",
            "subject": "Deploy finished",
            "text": "v2.4.0 is live.\r\nRollback plan: redeploy v2.3.9.",
        },
        "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    }, idempotency_key=str(uuid.uuid4()))
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "gmail__send-email",
        "arguments": {
          "to": "ops@example.com",
          "cc": "mona@acme.example",
          "subject": "Deploy finished",
          "text": "v2.4.0 is live.\r\nRollback plan: redeploy v2.3.9."
        }
      }
    }
    ```

    Expose the pack as tools with `--packs gmail` on the local server or `?packs=gmail` on the remote URL, and the agent calls `gmail__send-email` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Send email (documented response shape, example values). From the bundle's `fixtures/send-email.json`, not a live call.

```json
{
  "id": "18c9b0c1d2e3f4a5",
  "thread_id": "18c9b0c1d2e3f4a5",
  "labels": ["SENT"]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | built from the service's response |
| `connection_unauthorized` | 401 | no | Google rejected the access token (expired or revoked); refresh or reconnect it. |
| `rate_limited` | 429, 403 | yes | built from the service's response |
| `forbidden` | 403 | no | built from the service's response |
| `invalid_request` | 400 | no | built from the service's response |
| `conflict` | 409 | no | built from the service's response |

create-draft

**Create draft.** Save a plain-text email as a draft in the connected mailbox for a person to review and send. Returns the draft id and message id. Not idempotent.

Ref `io.b3os/gmail/create-draft@1.0.0` · effect `write` · connection `google` · MCP tool `gmail__create-draft`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Idempotency-Key: 7c1e2f6a-1d43-4a8e-b2a5-3f0c9d5e8b11" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/gmail/create-draft@1",
          "input": {
            "to": "customer@example.com",
            "subject": "Your refund",
            "text": "Hi, your refund was processed today."
          },
          "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/gmail/create-draft@1",
      input: {
        to: "customer@example.com",
        subject: "Your refund",
        text: "Hi, your refund was processed today.",
      },
      connections: { google: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    }, { idempotencyKey: crypto.randomUUID() });
    console.log(result.output);
    ```
  **Python**

```python
    import uuid
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/gmail/create-draft@1",
        "input": {
            "to": "customer@example.com",
            "subject": "Your refund",
            "text": "Hi, your refund was processed today.",
        },
        "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    }, idempotency_key=str(uuid.uuid4()))
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "gmail__create-draft",
        "arguments": {
          "to": "customer@example.com",
          "subject": "Your refund",
          "text": "Hi, your refund was processed today."
        }
      }
    }
    ```

    Expose the pack as tools with `--packs gmail` on the local server or `?packs=gmail` on the remote URL, and the agent calls `gmail__create-draft` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Create draft (documented response shape, example values). From the bundle's `fixtures/create-draft.json`, not a live call.

```json
{
  "draft_id": "r-1234567890123456789",
  "message_id": "18c9b1d2e3f4a5b6",
  "thread_id": "18c9b1d2e3f4a5b6"
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | built from the service's response |
| `connection_unauthorized` | 401 | no | Google rejected the access token (expired or revoked); refresh or reconnect it. |
| `rate_limited` | 429, 403 | yes | built from the service's response |
| `forbidden` | 403 | no | built from the service's response |
| `invalid_request` | 400 | no | built from the service's response |
| `conflict` | 409 | no | built from the service's response |

modify-labels

**Change message labels.** Add and remove labels on a message: remove UNREAD to mark it read, remove INBOX to archive it, add STARRED to star it, or add a user label. Returns the labels after the change.

Ref `io.b3os/gmail/modify-labels@1.0.0` · effect `write` · connection `google` · MCP tool `gmail__modify-labels`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Idempotency-Key: 7c1e2f6a-1d43-4a8e-b2a5-3f0c9d5e8b11" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/gmail/modify-labels@1",
          "input": {"id": "18c9a2b3d4e5f6a7", "remove": ["UNREAD", "INBOX"]},
          "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/gmail/modify-labels@1",
      input: { id: "18c9a2b3d4e5f6a7", remove: ["UNREAD", "INBOX"] },
      connections: { google: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    }, { idempotencyKey: crypto.randomUUID() });
    console.log(result.output);
    ```
  **Python**

```python
    import uuid
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/gmail/modify-labels@1",
        "input": {"id": "18c9a2b3d4e5f6a7", "remove": ["UNREAD", "INBOX"]},
        "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    }, idempotency_key=str(uuid.uuid4()))
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {
        "name": "gmail__modify-labels",
        "arguments": {"id": "18c9a2b3d4e5f6a7", "remove": ["UNREAD", "INBOX"]}
      }
    }
    ```

    Expose the pack as tools with `--packs gmail` on the local server or `?packs=gmail` on the remote URL, and the agent calls `gmail__modify-labels` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: Change message labels (documented response shape, example values). From the bundle's `fixtures/modify-labels.json`, not a live call.

```json
{
  "id": "18c9a2b3d4e5f6a7",
  "labels": ["IMPORTANT", "CATEGORY_PERSONAL"]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | built from the service's response |
| `connection_unauthorized` | 401 | no | Google rejected the access token (expired or revoked); refresh or reconnect it. |
| `rate_limited` | 429, 403 | yes | built from the service's response |
| `forbidden` | 403 | no | built from the service's response |
| `invalid_request` | 400 | no | built from the service's response |
| `conflict` | 409 | no | built from the service's response |

list-labels

**List labels.** The mailbox's labels: system labels (INBOX, SENT, UNREAD, STARRED, ...) and user labels with their ids, which search and modify-labels use.

Ref `io.b3os/gmail/list-labels@1.0.0` · effect `read` · connection `google` · MCP tool `gmail__list-labels`

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/actions/call \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
          "ref": "io.b3os/gmail/list-labels@1",
          "input": {},
          "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"}
        }'
    ```
  **TypeScript**

```ts
    import { B3OS } from "@b3os/sdk";

    const b3os = new B3OS();
    const result = await b3os.actions.call({
      ref: "io.b3os/gmail/list-labels@1",
      input: {},
      connections: { google: "conn_01m5t6cbgdjs1ketgcqwa2sne8" },
    });
    console.log(result.output);
    ```
  **Python**

```python
    from b3os import B3OS

    b3os = B3OS()
    result = b3os.actions.call({
        "ref": "io.b3os/gmail/list-labels@1",
        "input": {},
        "connections": {"google": "conn_01m5t6cbgdjs1ketgcqwa2sne8"},
    })
    print(result["output"])
    ```
  **MCP**

```json
    {
      "method": "tools/call",
      "params": {"name": "gmail__list-labels", "arguments": {}}
    }
    ```

    Expose the pack as tools with `--packs gmail` on the local server or `?packs=gmail` on the remote URL, and the agent calls `gmail__list-labels` with the action's input. The tool uses your newest connection for each key without a `subject`. Without exposing the pack, `actions_call` takes the same input with `ref`.

Output, labeled `fixture`: List labels (documented response shape, example values). From the bundle's `fixtures/list-labels.json`, not a live call.

```json
{
  "labels": [
    {"id": "INBOX", "name": "INBOX", "type": "system"},
    {"id": "UNREAD", "name": "UNREAD", "type": "system"},
    {"id": "Label_12", "name": "Customers", "type": "user"}
  ]
}
```

When the service answers with an error, the call fails with [`upstream_error`](/errors#upstream_error) and `pack_error.code` is one of:

| `pack_error.code` | Service status | Retryable | Message |
|---|---|---|---|
| `not_found` | 404 | no | built from the service's response |
| `connection_unauthorized` | 401 | no | Google rejected the access token (expired or revoked); refresh or reconnect it. |
| `rate_limited` | 429, 403 | yes | built from the service's response |
| `forbidden` | 403 | no | built from the service's response |
| `invalid_request` | 400 | no | built from the service's response |
| `conflict` | 409 | no | built from the service's response |