---
title: Gmail
description: Turn labelled Gmail threads into agent conversations via the Chat SDK.
type: channel
keywords:
  - chat sdk
  - gmail
  - email
  - google workspace
  - pubsub
  - oauth
---

# Gmail

Channel integration for eve. Turn labelled Gmail threads into agent conversations via the Chat SDK.

## Install

Add this Chat SDK channel from eve's registry. This writes `agent/channels/gmail.ts` and installs Chat SDK and its adapter dependencies:

```bash
eve add channel/chat-sdk-gmail
```

## Quick start

Create `agent/channels/gmail.ts`:

```ts
// agent/channels/gmail.ts
import { createGmailAdapter } from "@chat-adapter/gmail";
import { createRedisState } from "@chat-adapter/state-redis";
import type { Message, Thread } from "chat";
import { chatSdkChannel } from "eve/channels/chat-sdk";

export const gmail = createGmailAdapter();

export const { bot, channel, send } = chatSdkChannel({
  userName: "My Agent",
  adapters: { gmail },
  state: createRedisState({ keyPrefix: "gmail-agent" }),
  // Gmail sends email once and cannot edit an in-progress response.
  streaming: false,
});

bot.onNewMention(async (thread: Thread, message: Message) => {
  await thread.subscribe();
  await send(message.text, { thread });
});

bot.onSubscribedMessage(async (thread: Thread, message: Message) => {
  await send(message.text, { thread });
});

await bot.initialize();

export default channel;
```

**Installing or deploying this channel does not start Gmail listening.** After configuring credentials and Pub/Sub, run the maintenance job once as described under [Configure](#configure). Otherwise, registration waits until the first successful daily job.

Gmail receives only messages with the configured handoff label. Responses are sent after the turn completes because email cannot edit an in-progress response. The registry installs Redis-backed state so Gmail's cursor, delivery receipts, and lock survive serverless invocations. See the [Gmail adapter documentation](https://chat-sdk.dev/adapters/official/gmail) for credentials, label selection, and delivery semantics.

## Configure

Enable the Gmail API and Pub/Sub, then configure user-context OAuth for the mailbox, an authenticated wrapped Pub/Sub push subscription, and a Gmail handoff-label ID. Set `GMAIL_MAILBOX`, `GMAIL_LABEL_ID`, `GMAIL_CLIENT_ID`, `GMAIL_CLIENT_SECRET`, `GMAIL_REFRESH_TOKEN`, `GMAIL_PUBSUB_AUDIENCE`, `GMAIL_PUBSUB_SERVICE_ACCOUNT_EMAIL`, `GMAIL_SUBSCRIPTION`, and `GMAIL_TOPIC_NAME`. Set `REDIS_URL` to a durable Redis connection URL; Upstash REST credentials are not compatible with this adapter. The adapter mounts its authenticated Pub/Sub webhook at `/eve/v1/gmail`.

The registry also writes `agent/schedules/gmail-maintenance.ts`, which calls `gmail.watch()` on a daily `0 9 * * *` schedule (09:00 UTC on Vercel) to register or renew Gmail's watch. Adjust the cadence for your host and plan. Incoming Pub/Sub webhooks run synchronization from the saved cursor; the schedule does not process messages. Do not call `gmail.sync()` from this schedule or at startup: the generated message handlers use `send()`, which requires an active Chat SDK webhook context. Missed changes can be picked up by a later successful webhook, but this scaffold does not provide an independent recovery sync.

### Start listening — required after deployment

**Run the maintenance job once after configuring credentials, deploying, and setting up Pub/Sub.** Installing the channel or deploying the app does not register Gmail's watch. Without this step, registration waits until the first successful daily job.

From your linked Vercel project:

```bash
vercel crons list
vercel crons run <maintenance-job-path>
```

Replace `<maintenance-job-path>` with the Gmail maintenance route listed by the first command. Confirm the job succeeds in Vercel runtime logs before testing email. The job calls `gmail.watch()` using the deployed credentials and Redis state; the daily schedule then renews it. You do not need a faster cron schedule to start listening immediately.

**Existing labelled mail is not imported on first setup.** The first successful `watch()` initializes the cursor at registration time. Send a new message from another account and apply the handoff label after initialization. Later watch renewals preserve the existing cursor. See the [Gmail adapter setup guide](https://chat-sdk.dev/adapters/official/gmail#setup) for Google Cloud, Pub/Sub, OAuth scopes, and watch-renewal requirements.

[Read the full channel documentation](/docs/channels/chat-sdk)

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)