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

# Gmail agent with file attachments

> An agent that sends email with attachments - the model picks files by name, your code controls the bytes.

This is the [monday assistant loop](/examples/monday-agent) plus one new capability: **uploading files in a tool call**. It demonstrates the pattern behind every file-accepting tool.

## How files work in an agent loop

Gmail's send-email tool declares its attachments field as a file input (`"format": "engini/file"` in the tool schema). Two things follow:

1. **The model never produces base64.** When Engini wraps the schema for the model, file fields become plain *string* fields. The model picks a file by **name** (a reference key), not by content.
2. **You decide what those names resolve to.** You hand `handle_tool_calls` a registry of files; the SDK swaps the key the model chose for the real file and encodes it on the way out.

## The recipe

```python theme={null}
from pathlib import Path
from engini import Engini, File
from engini.providers.anthropic import AnthropicProvider

client = Engini(provider=AnthropicProvider())   # reads ENGINI_API_KEY
conn_id = next(c.connection_id for c in client.connections.list(application="gmail"))

toolset = client.toolset(connections={"gmail": conn_id})
tools = toolset.tools()          # the attachments field appears as string keys

# The files the agent MAY attach, referenced by name
def load_attachments(directory: Path) -> dict[str, File]:
    return {
        p.name: File.from_path(p)
        for p in sorted(directory.iterdir())
        if p.is_file() and not p.name.startswith(".")
    }

files = load_attachments(Path("files"))
```

Tell the model what's available in the system prompt:

```python theme={null}
system = (
    "You are an assistant that sends email through the user's Gmail account "
    "via Engini tools. Always confirm the recipient, subject, and attachments. "
    "These files are available to attach, referenced by name: "
    + ", ".join(repr(name) for name in files)
)
```

The loop is identical to the monday agent, with one changed line - the file registry:

```python theme={null}
results = toolset.handle_tool_calls(reply, files=files)
```

The model decides *which* file to attach (by name); your code controls *what* those names resolve to. The model never sees file bytes. An unknown reference key raises `EnginiValidationError`.

## Without an LLM

Outside an agent loop, pass a `File` straight into the tool call:

```python theme={null}
client.tools.execute(
    "gmail_send_mail",
    {
        "to": "alex@acme.com",
        "subject": "Q3",
        "body": "See attached.",
        "attachmentsarray": [File.from_path("q3-report.pdf")],
    },
    connection_id=conn_id,
)
```

## Run the original

[`python/examples/gmail-agent/`](https://github.com/engini/engini-sdk/tree/main/python/examples/gmail-agent) - includes a sample attachment; drop your own files into its `files/` folder:

```bash theme={null}
cd python/examples/gmail-agent
uv run agent.py "Email alex@acme.com the Q3 report and mention it's attached"
```

More on file handling (constructors, mime types, CLI `--file`): [File inputs](/sdk/files).
