Gmail connector setup
Operator·5 minutes to read
This page is the setup half — creating the OAuth app Gmail needs in Google's own console and getting its client id and secret into this deployment. Authorising a connection afterwards is the Authenticate control on the plugin sheet, which is what actually sends you to Google.
1. Create the OAuth app in Google Cloud
-
Open the Google Cloud console and create a project (or pick an existing one) — this is your own project, not Botseon's.
-
APIs & Services → Library: enable the Gmail API.
-
APIs & Services → OAuth consent screen: choose External (unless your Google Workspace restricts it to internal), fill in an app name and your own e-mail as the support and developer contact. Publishing status can stay Testing for personal use — Google shows an "unverified app" screen for a testing app, which you will see and click through once; that screen, not a shared secret, is the actual cost of bringing your own app.
-
APIs & Services → Credentials → Create Credentials → OAuth client ID, application type Web application.
-
Under Authorized redirect URIs, add the redirect URL from
docs/connectors/README.md— with the quickstart's defaultBOTSEON_PUBLIC_URL, that is:http://127.0.0.1:3000/api/oauth/gmail/callback -
Create it. Google shows you a client ID and a client secret once — copy both now.
2. Scopes
Request the narrowest set that covers triage-and-draft use (F-PLUG-1: minimum scopes per capability, read and write requested separately):
https://www.googleapis.com/auth/gmail.readonly— read messages and threads; no writes.https://www.googleapis.com/auth/gmail.modify— read, label, archive, draft and move to the bin. Covers "draft a reply for approval" without granting permanent deletion or mailbox-settings access.https://www.googleapis.com/auth/gmail.send— send a message the member approved.
The connector's catalogue row is what actually requests scopes at authorisation time (the scopes
field in extensions/connectors/gmail/manifest.json: the read scope on Authenticate, all
three on Allow sending); these are the three to have configured and ready on the OAuth consent
screen above.
3. Enter the app
One command, with the client secret on standard input — there is no flag that takes it and no
environment variable that holds it (see
docs/connectors/README.md):
printf %s "$CLIENT_SECRET" | botseon oauth-app set \
--org <organisation id> --slug gmail \
--client-id <the client ID from step 1> \
--auth-url https://accounts.google.com/o/oauth2/v2/auth \
--token-url https://oauth2.googleapis.com/token \
--revoke-url https://oauth2.googleapis.com/revoke \
--configured-by <your user id>
It stores the secret encrypted and prints the redirect URL from step 1 back, so you can check the
two match. botseon oauth-app show --org <organisation id> --slug gmail says whether an app is
configured and under which client id; nothing prints the secret back.
Never put either value in a bot's description, a memory, or anywhere else a model reads as ordinary text.
4. What a bot can do with the connection
| Tool | What it does | Asks the member first? |
|---|---|---|
gmail.search, gmail.get_message | Find and read mail; a message lists each attachment's name, type and size | No |
gmail.get_attachment | Read a text or PDF attachment's text (a scan or an image is reported, not guessed) | No |
gmail.create_draft | Leave a draft, with any attached files, in the member's Drafts; nothing is sent | Yes, unless the member always-allows it |
gmail.apply_labels, gmail.create_label | Label mail, and create a label | Yes, unless the member always-allows it |
gmail.reply, gmail.send, gmail.forward | Send mail; the card names the recipient and lists any attached file | Yes, unless the member always-allows it |
gmail.trash | Move a message to the bin (recoverable in Gmail) | Yes, every time: no always-allow rule covers it |
5. Attached files: what to check when one does not go out
gmail.send, gmail.reply and gmail.create_draft take an attachments list: a file already in
the mailbox (messageId and the attachmentId that gmail.get_message lists) or a text file the
bot writes (filename and content). A mail with files goes up through Gmail's upload endpoint
(https://gmail.googleapis.com/upload/gmail/v1/users/me/...?uploadType=multipart), on the same host
as every other Gmail call, so nothing changes in your egress rules. No extra scope is needed: the
three above already cover the download, the draft and the send. The request shapes and the limits
are in
docs/research/connectors/gmail-attachments-2026-10-21.md.
A member who reports "the file did not go out" has had one of these sentences in the run. Each is a refusal the driver gives before anything is sent:
| The run says | What it means | What to do |
|---|---|---|
| "One of the files is a type Gmail blocks …" | A program or script file (Gmail's own list of 54 types) | Nothing on your side: Gmail never sends these. The member can share it another way, such as a Drive link |
| "The files add up to more than 25 MB …" | Over Gmail's limit for one mail | The member sends fewer files per mail |
| "Each attachment is either a file already in the mailbox … or a text file to write …" | The model mixed up the two kinds | Nothing to fix; the bot corrects the call |
| "that message has no such attachment" | The attachment id is not on that message (often an old id after the message changed) | The bot reads the message again and uses the id it lists now |
| "Gmail rejected that request as invalid" | Gmail's own 400, for example an archive with a blocked file inside, or a Workspace admin's lower size limit | Check the Workspace admin's Gmail attachment settings |
The nightly connector-live run checks this path against the Google test account: the case
"attaches a mailbox file and a written file to a mail, and reads both back" in
extensions/connectors/gmail/driver.live.test.ts. If it fails while the other Gmail cases pass, the
upload endpoint or its shape changed. Read the upload guide again and update the research note.
Last verified against build c0f77aa.