# Gmail Labeler (/cookbooks/gmail-labeler)

With Devcaster's managed authentication, tool calling and triggers, it's easy to
build the AI agents that can interact and react the real world events reducing
the boilerplate required to setup and manage the authentication. This cookbook
will walk you through the process of building agents using `Devcaster`, `LangChain`.

Prerequisites [#prerequisites]

* Python3.x
* [UV](https://docs.astral.sh/uv/getting-started/installation/)
* Devcaster API key
* OpenAI API key
* Understanding of building AI agents (Preferably with LangChain)

Build gmail agent to label your messages [#build-gmail-agent-to-label-your-messages]

```python
from devcaster import Devcaster
from devcaster_langchain import LangchainProvider

from langchain import hub
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_openai import ChatOpenAI

def create_agent(user_id: str, devcaster_client: Devcaster[LangchainProvider]):
    """
    Create an agent for a given user id.
    """
    # Step 1: Get all the tools
    tools = devcaster_client.tools.get(
        user_id=user_id,
        tools=[
            "GMAIL_LIST_LABELS",
            "GMAIL_ADD_LABEL_TO_EMAIL",
            "GMAIL_CREATE_LABEL",
        ],
    )

    # Step 2: Pull relevant agent prompt.
    prompt = hub.pull("hwchase17/openai-functions-agent")

    # Step 3: Initialize chat model.
    openai_client = ChatOpenAI(model="gpt-5")

    # Step 4: Define agent
    return AgentExecutor(
        agent=create_openai_functions_agent(
            openai_client,
            tools,
            prompt,
        ),
        tools=tools,
        verbose=False,
    )
```

Authenticating users [#authenticating-users]

To authenticate your users with Devcaster you need an auth config for the given
app, In this case you need one for gmail. You can create and manage auth configs
from the [dashboard](https://platform.devcaster.dev/?next_page=/auth-configs?create_auth_config=gmail).
Devcaster platform provides devcaster managed authentication for some apps to help
you fast-track your development, `gmail` being one of them. You can use these
default auth configs for development, but for production you should always use
your own oauth app configuration.

Using dashboard is the preferred way of managing authentication configs, but if
you want to do it manually you can follow the guide below

<details>
<summary>
Click to expand
</summary>

To create an authentication config for `gmail` you need `client_id` and `client_secret`
from your from your [Google OAuth Console](https://developers.google.com/identity/protocols/oauth2).
Once you have the required credentials you can use the following piece of
code to set up authentication for `gmail`.

```python
from devcaster import Devcaster
from devcaster_langchain import LangchainProvider

def create_auth_config(devcaster_client: Devcaster[OpenAIProvider]):
    """
    Create a auth config for the gmail toolkit.
    """
    client_id = os.getenv("GMAIL_CLIENT_ID")
    client_secret = os.getenv("GMAIL_CLIENT_SECRET")
    if not client_id or not client_secret:
        raise ValueError("GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET must be set")

    return devcaster_client.auth_configs.create(
        toolkit="gmail",
        options={
            "name": "default_gmail_auth_config",
            "type": "use_custom_auth",
            "auth_scheme": "OAUTH2",
            "credentials": {
                "client_id": client_id,
                "client_secret": client_secret,
            },
        },
    )
```

This will create an authentication config for `gmail` which you can use to
authenticate your users for your app. Ideally you should just create one
authentication object per project, so check for an existing auth config
before you create a new one.

```python
def fetch_auth_config(devcaster_client: Devcaster[OpenAIProvider]):
    """
    Fetch the auth config for a given user id.
    """
    auth_configs = devcaster_client.auth_configs.list()
    for auth_config in auth_configs.items:
        if auth_config.toolkit == "gmail":
            return auth_config

    return None
```
</details>

Once you have authentication management in place, we can start with connecting
your users to your `gmail` app. Let's implement a function to connect the users
to your `gmail` app via devcaster.

```python
# Function to initiate a connected account
def create_connection(devcaster_client: Devcaster[OpenAIProvider], user_id: str):
    """
    Create a connection for a given user id and auth config id.
    """
    # Fetch or create the auth config for the gmail toolkit
    auth_config = fetch_auth_config(devcaster_client=devcaster_client)
    if not auth_config:
        auth_config = create_auth_config(devcaster_client=devcaster_client)

    # Create a connection for the user
    return devcaster_client.connected_accounts.initiate(
        user_id=user_id,
        auth_config_id=auth_config.id,
    )
```

Now, when creating tools for your agent always check if the user already has a
connected account before creating a new one.

```python
def check_connected_account_exists(
    devcaster_client: Devcaster[LangchainProvider],
    user_id: str,
):
    """
    Check if a connected account exists for a given user id.
    """
    # Fetch all connected accounts for the user
    connected_accounts = devcaster_client.connected_accounts.list(
        user_ids=[user_id],
        toolkit_slugs=["GMAIL"],
    )

    # Check if there's an active connected account
    for account in connected_accounts.items:
        if account.status == "ACTIVE":
            return True

        # Ideally you should not have inactive accounts, but if you do, you should delete them
        print(f"[warning] inactive account {account.id} found for user id: {user_id}")
    return False
```

Creating Triggers [#creating-triggers]

You can use triggers to make your agents react to real world events. In this example,
we will use triggers to invoke your agent everytime there's a new message in your
gmail inbox.

```python
# Create a new trigger
def create_trigger(
    devcaster_client: Devcaster[LangchainProvider],
    connected_account_id: str,
) -> str:
    """
    Create a trigger.
    """
    response = devcaster_client.triggers.create(
        slug="GMAIL_NEW_GMAIL_MESSAGE",
        connected_account_id=connected_account_id,
        trigger_config={},
    )
    return response.trigger_id
```

When creating triggers, make sure there are no duplicate triggers. Use following
code as reference for checking if trigger for given connected account exists or not.

```python
def check_trigger_exists(
    devcaster_client: Devcaster[LangchainProvider],
    connected_account_id: str,
) -> t.Optional[str]:
    """
    Check if a trigger exists.
    """
    triggers = devcaster_client.triggers.list_active(
        trigger_names=["GMAIL_NEW_GMAIL_MESSAGE"],
        connected_account_ids=[connected_account_id],
    )
    for trigger in triggers.items:
        return trigger.id
    return None
```

Once trigger is created, you can listen to events using a trigger subscription.

```python
# Create subscription object
trigger_subscription = devcaster_client.triggers.subscribe()

# Register event handler
@trigger_subscription.handle(
    trigger_id="",  # Filter out events that does not belong this trigger id
    trigger_slug="GMAIL_NEW_GMAIL_MESSAGE",
)
def handle_event(event: TriggerEvent):
    print("> Received email with subject: ", event["payload"]["subject"])

# Wait for events
trigger_subscription.wait_forever()
```

Putting everything together [#putting-everything-together]

Let's put together everything by making the agent react to new messages in your
inbox.

```python
# Create a trigger subscription factory

def create_trigger_subscription(
    devcaster_client: Devcaster[LangchainProvider],
    trigger_slug: str,
    trigger_id: str,
    agent: AgentExecutor,
):
    """
    Create a trigger subscription for the given agent.
    """
    trigger_subscription = devcaster_client.triggers.subscribe()

    @trigger_subscription.handle(
        trigger_slug=trigger_slug,
        trigger_id=trigger_id,
    )
    def handle_event(event: TriggerEvent):
        print("> Received email with subject: ", event["payload"]["subject"])
        result = agent.invoke(
            input={
                "input": APPLY_NEW_LABEL.format(  # Check `gmail_labeler/prompt.py`
                    message_id=event["payload"]["id"],
                    message_subject=event["payload"]["subject"],
                    message_text=event["payload"]["message_text"],
                )

        )
        print("> Result: ", result["output"])

    return trigger_subscription
```

Package everything as a single entry point.

```python
def run_agent(user_id: str):
    # Create devcaster client
    devcaster_client = Devcaster(provider=LangchainProvider())

    # Validate conected account
    connected_account_id = check_connected_account_exists(devcaster_client, user_id)
    if connected_account_id is None:
        connection_request = create_connection(devcaster_client, user_id)
        print(
            f"Authenticate with the following link: {connection_request.redirect_url}"
        )
        connection_request.wait_for_connection()
        connected_account_id = connection_request.id

    # Check if trigger exists, create if not
    trigger_id = check_trigger_exists(
        devcaster_client=devcaster_client,
        connected_account_id=connected_account_id,
    )
    if trigger_id is None:
        trigger_id = create_trigger(
            devcaster_client=devcaster_client,
            connected_account_id=connected_account_id,
        )

    # Create agent
    agent = create_agent(user_id=user_id, devcaster_client=devcaster_client)

    # Create trigger subscription
    trigger_subscription = create_trigger_subscription(
        devcaster_client=devcaster_client,
        trigger_slug=GMAIL_NEW_GMAIL_MESSAGE_TRIGGER,
        trigger_id=trigger_id,
        agent=agent,
    )

    # Wait forever
    print("Waiting for events...")
    trigger_subscription.wait_forever()
```

To test the above function as CLI, follow the steps below

1. Clone the repository
```bash
git clone git@github.com:devcasterhq/gmail-labeler
cd gmail-labeler/
```
2. Setup environment

```bash
cp .env.example .env
```

Fill the api keys

```dotenv
DEVCASTER_API_KEY=
OPENAI_API_KEY=
```

Create the virtual env

```bash
make env
source .venv/bin/activate
```
3. Run the agent
```bash
python gmail_labeler --user-id "default"
```

Using Devcaster for managed auth and tools [#using-devcaster-for-managed-auth-and-tools]

Devcaster reduces a lot of boilerplate for building AI agents with ability access and use a wide variety of apps. For example in this cookbook, to build `gmail` integration without devcaster you would have to write code to

* manage `gmail` oauth app
* manage user connections
* tools for your agents to interact with `gmail`
* Infra for listening to changes in your gmail inbox

Using devcaster simplifies all of the above to a few lines of code as we've seen the cookbook.

Best practices [#best-practices]

**🔒 User Management**:

* Use unique, consistent `user_id` values for each person
* Each user maintains their own gmail connection
* User IDs can be email addresses, usernames, or any unique identifier

Troubleshooting [#troubleshooting]

**Connection Issues**:

* Ensure your `.env` file has valid `DEVCASTER_API_KEY` and `OPENAI_API_KEY`
* Check that the user has completed `gmail` authorization
* Verify the user\_id matches exactly between requests

**API Errors**:

* Check the server logs for detailed error messages
* Ensure request payloads match the expected format
* Visit `/docs` endpoint for API schema validation

---

📚 **More documentation:** [View all docs](https://docs.devcaster.dev/llms.txt) | [Cookbooks](https://docs.devcaster.dev/llms.mdx/cookbooks) | [API Reference](https://docs.devcaster.dev/llms.mdx/reference)

---

# Devcaster SDK — Instructions for AI Code Generators

**Purpose:** Enforce only the **current** and **correct** patterns for integrating [Devcaster](https://devcaster.dev/) tools into AI agents.
**Scope:** All AI-generated code using Devcaster must follow these guardrails.

---

## 1. Correct Devcaster Integration

Devcaster supports two integration modes: **Native Tools** (with a provider package) and **MCP** (no provider package needed).

### Native Tools

```python
# ✅ CORRECT — Python (defaults to OpenAI)
from devcaster import Devcaster

devcaster = Devcaster()
session = devcaster.create(user_id="user_123")
tools = session.tools()
# Pass tools to your agent/LLM framework
```

```typescript
// ✅ CORRECT — TypeScript (defaults to OpenAI)
import { Devcaster } from "@devcaster/core";

const devcaster = new Devcaster();
const session = await devcaster.create("user_123");
const tools = await session.tools();
// Pass tools to your agent/LLM framework
```

For other providers, pass the provider explicitly. Provider packages follow the naming convention: `devcaster_<provider>` for Python, `@devcaster/<provider>` for TypeScript.

### MCP

Use `session.mcp.url` and `session.mcp.headers` with any MCP-compatible client (Claude Desktop, Cursor, OpenAI Agents, etc.). No provider package needed.

---

## 2. CRITICAL INSTRUCTIONS FOR AI MODELS

### 2.1 — ALWAYS DO THE FOLLOWING

1. **Use `devcaster.create(user_id)`** to create a session — this is the entry point for all Devcaster integrations.
2. **Use `session.tools()`** for native tool integration or **`session.mcp.url`** for MCP integration.
3. **Import** from the correct provider package (`devcaster_<provider>` for Python, `@devcaster/<provider>` for TypeScript).
4. **Pass `user_id` to `devcaster.create()`**, not to individual tool calls.

### 2.2 — NEVER DO THE FOLLOWING

1. **Do not** instruct users to manually create auth configs or connected accounts before running.
2. **Do not** import from `devcaster_openai` / `@devcaster/openai` when using OpenAI Agents SDK — use `devcaster_openai_agents` / `@devcaster/openai-agents` instead.

### 2.3 — DISCOURAGED (do not use unless user explicitly asks)

Do **not** generate `devcaster.tools.get()`, `devcaster.tools.execute()`, or `devcaster.provider.handle_tool_calls()` unless the user explicitly requests direct tool execution. These are a supported but **not recommended** low-level interface.

