MainSDK
Search⌘K
How-to

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.

Building for 0.3

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
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

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.

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

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

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.

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet