---
title: "Let an agent act in a customer's Gmail or GitHub"
description: "Connect a person's Google, GitHub or Slack account once, give an app agent only the scopes it needs, and make risky writes wait for a yes."
section: "guides"
status: "Building for 0.3"
spec: "connections"
canonical: "https://mainsdk.dev/docs/guides/connect-accounts"
updated: "2026-10-28"
---

# Let an agent act in a customer's Gmail or GitHub

> Connect a person's Google, GitHub or Slack account once, give an app agent only the scopes it needs, and make risky writes wait for a yes.

Status: Building for 0.3 · Canonical: https://mainsdk.dev/docs/guides/connect-accounts · Updated: 2026-10-28

Your app agent needs to read a person's Gmail, or open an issue in their GitHub, and only after they agreed. This guide sets up one provider, one agent with a narrow scope and one risky scope that waits for a yes. It takes about 30 minutes.

Spec `connections`, for 0.3. Status: experimental, so import it from `mainsdk/experimental/connections`.

## You need

- An OAuth app registered with the provider, with its client ID and secret. Its redirect URL is your app's `connections.callback` path.
- A MainSDK app with an agent defined in `main.yaml`.

## 1. Declare the provider and the agent

```yaml title="main.yaml"
connections:
  store: db
  callback: /api/connections/callback
  providers:
    gmail:
      preset: google
      client-id: 1234.apps.googleusercontent.com
      client-secret: secret:GOOGLE_OAUTH_SECRET
      scopes: [https://www.googleapis.com/auth/gmail.readonly, https://www.googleapis.com/auth/gmail.send]
      hosts: [gmail.googleapis.com]
      step-up: [https://www.googleapis.com/auth/gmail.send]   # sending waits for a person's yes
agents:
  support-bot:
    connections:
      gmail: [https://www.googleapis.com/auth/gmail.readonly]  # read only, for this agent
```

`scopes` is the most the app may ever ask for. The agent gets the read scope alone.

## 2. Send the person to the consent screen

```ts title="app/connect/route.ts"
import { connections } from "mainsdk/experimental/connections";

export async function GET(req: Request) {
  return connections.connect(req, "gmail", { returnTo: "/settings/accounts" });
}
```

The person sees one consent screen. After they agree, the callback stores the grant and returns them to `returnTo`.

## 3. Show what they agreed to

```ts title="app/settings/accounts.ts"
import { connections } from "mainsdk/experimental/connections";

const info = connections.describe("gmail");
```

Render `info.scopes` and `info.agents` on your settings page, so the person can see which agent can do what.

## 4. Let the agent read

```ts title="app/agents/support.ts"
import { connections } from "mainsdk/experimental/connections";

declare const user: { id: string };   // the signed-in person

const res = await connections.fetch("gmail", "https://gmail.googleapis.com/gmail/v1/users/me/messages", {
  scope: "https://www.googleapis.com/auth/gmail.readonly",
  agent: "support-bot",
  user,
});
```

`scope` is required. A call for a scope the agent does not hold fails with `MAIN-CONN-0004` and nothing is sent.

## 5. Make the send wait for a yes

A call that needs `gmail.send` returns `Pending` until the person approves it in your app. Handle `Pending` like any other waiting state, and render the approval request.

## Check it worked

- After connecting, `connections.list()` shows the grant with `status: "active"`.
- `connections.fetch` with the read scope returns a response.
- The same call with a scope not listed under `agents.support-bot` fails with `MAIN-CONN-0004`.
- Revoking the grant with `connections.revoke(id)` makes the next call fail with `MAIN-CONN-0002` or `MAIN-CONN-0007`.

## If it fails

- `MAIN-CONN-0006`: the callback state was missing, expired or used. Start the connect flow again.
- `MAIN-CONN-0005`: the URL's host is not in `hosts`. Call the listed host only.
- `MAIN-CONN-0021`: a provider has no preset and no `hosts`. Add them.

## Prompt for your coding agent

> Connect a customer's Gmail with MainSDK's `connections` interface. Request only the read scope for `support-bot`, keep send behind `step-up`, and show each scope and agent on the account settings page.
