Skip to content

CONNECT AN API · GMAIL · 15 MINUTES

Connect Gmail

Gmail gives your agent three operations: list the mail in a mailbox, read one message, and send an email. The credential lives in your Enclave's vault on your own machine, and it never reaches EKKA.

This page is for whoever runs the mailbox an agent will work in, and for the engineer wiring the plan.

The part worth knowing before you start: an agent that can send mail can only send to addresses you named. The allowlist is on the plan step, and it is checked inside your Enclave before the message is built, so an address outside it never becomes an email at all.

You need Google Workspace and someone who can create an OAuth client in your Google Cloud project. Google has no client-credentials flow for Gmail, so connecting means obtaining a refresh token once and pasting it in. That is enrollment, and it happens a single time.


Which row do you want?

Two rows in the catalog, and picking the right one is the only decision you make.

gmail gmail-org
whose mailbox yours a shared company mailbox, e.g. [email protected]
who signs in you whoever owns the shared mailbox
who can cut it off you, on your own the mailbox owner or an admin
survives you leaving no yes
normally lives on your laptop a server

They are two rows on purpose. A grant saying an agent may use gmail can never reach the company mailbox, and a grant on gmail-org can never reach yours. If they shared one name, one permission would mean two very different things.

The rest of this page uses gmail. Swap in gmail-org and every command is the same.

A shared mailbox must be a real user account

A Google Group cannot be used. A group has no login, so it can neither consent nor hold a credential. Make one Workspace user, then add hello@, info@, support@ and the rest as aliases on it. Mail to any alias lands in that one mailbox.

Step 1 · Create the OAuth client

The client lives inside your own Google organization, so the consent screen your people see belongs to your company.

  1. In Google Cloud Console, create a project and enable the Gmail API.
  2. Under OAuth consent screen, choose Internal. An Internal client accepts accounts inside your Workspace and nowhere else, and its refresh tokens keep working. An External client left in Testing issues refresh tokens that stop working after seven days, which looks like EKKA breaking a week after you set it up.
  3. Under Credentials, create an OAuth client ID of type Web application, and add https://developers.google.com/oauthplayground as an authorized redirect URI.
  4. Copy the client ID and the client secret.

Step 2 · Get a refresh token

Consent happens once, in Google's own tooling, and it produces the long-lived value EKKA stores. Google's OAuth 2.0 Playground does it without you writing any code.

  1. Open the Playground, click the settings gear, and tick Use your own OAuth credentials. Paste the client ID and client secret from step 1.
  2. In the scope box enter https://www.googleapis.com/auth/gmail.modify, then click Authorize APIs.
  3. Sign in as the mailbox you want the agent to work in, and click Allow.
  4. Click Exchange authorization code for tokens, and copy the refresh token.

One permission, and where to take it back

gmail.modify covers reading mail and sending as that mailbox. Permanent delete sits outside the scope, so no plan can reach it. Google lists what you granted at myaccount.google.com/permissions, which is also where you revoke it.

Step 3 · Connect it on this machine

ekka api connect gmail --input client_id=<your client id>

It asks for the credential at a prompt. Paste the client secret and the refresh token, joined by a colon:

<client secret>:<refresh token>

For example, GOCSPX-aBc123:1//0gLmXyz.... A colon inside the refresh token is fine, because only the first colon splits the two values.

The credential is read from a prompt and never from an argument, because an argument lands in your shell history and in the process list where any other user on the machine can read it. For a script, --stdin reads it from standard input and is still never an argument.

What is written where

The secret goes into this machine's encrypted vault. The connection record beside it holds the address, the shape of the credential and its reference, and no values at all. A connection is per machine, because the credential lives on the machine and never leaves it.

Step 4 · Let an agent use it

connect prints this with your values already filled in:

ekka gate grant add --type api --resource apis/gmail \
  --capability api.read --no-fingerprint

api.read lets an agent list and read messages. Add a second grant with api.write when you want it to send as well.

Until you run this, nothing can touch the mailbox. Connecting and permitting are two separate decisions, so a connection sitting on a machine is not access.

Step 5 · Check it works

ekka api test gmail

It checks four things in order and prints a row for each: the config it will use, whether the credential is in the vault, whether a grant allows the operation, and a live call to GET /gmail/v1/users/me/profile. The live row is a real call to Google rather than a guess from stored state.

It exits 0 when the connection works, 1 when something is wrong, and 2 when a check could not be performed. A check that cannot run says so and never reports as a pass.


What a plan can do

Three operations. See the exact fields with ekka api spec gmail.

Call What it does Needs
listMessages list message ids in the mailbox nothing. query is optional
readMessage read one message id
sendMail send an email to, subject, text, and a recipients list

Read the unread mail from the last day:

{ "id": "unread", "action_ref": "ekka.gate.api.v1",
  "target": "<your api gate>", "op": "read", "call": "listMessages",
  "inputs": { "resource": "apis/gmail",
              "query": "is:unread newer_than:1d" } }

query is Gmail's own search syntax, the same thing you type in the Gmail search box. readMessage takes a format of metadata when you want headers only, which is what a triage agent wants.

Send a reply:

{ "id": "reply", "action_ref": "ekka.gate.api.v1",
  "target": "<your api gate>", "op": "write", "call": "sendMail",
  "inputs": { "resource": "apis/gmail",
              "to": "@input.customer",
              "subject": "Re: your order",
              "text": "@op.draft.output.text",
              "recipients": ["*@acme.com"] } }

A value may be @input.<name> to take it from the run. The target is your Enclave's api gate, and ekka gate list names it.

Who may your agent email?

sendMail will not run without a recipients list. It is not a setting you can leave off:

"recipients": ["*@acme.com", "[email protected]"]

Write whole addresses, or *@domain for everyone at a domain.

The check runs inside your Enclave, before the message is built, so an address outside the list never becomes an email. That matters because the address usually comes from a model:

"to": "@input.customer"

Your plan does not know at authoring time what that will be. The allowlist is what makes "the agent decides who to email" safe to write down.

Why you cannot allow everyone

A bare * is refused. There is no way to spell "any recipient", because a list that permitted everything would read like a protection while being none, and reviewing it would tell you nothing. To send anywhere, the operation must not restrict the field at all.

What can an agent not do here, even with a grant?

  • It cannot email as anyone else. from is not an input. Gmail sets it from the authenticated mailbox, which is the only value it can be trusted to be.
  • It cannot reach another mailbox. Every path is users/me, the mailbox the refresh token was issued for. There is no input that would let a plan point somewhere else.
  • Govern never sees your mail. Gmail wants a whole RFC 5322 message, base64url encoded into a raw field. Your Enclave assembles that from the fields you supply. The control plane authorizes the call and signs the receipt without the message body crossing it.
  • Revoking is immediate. An access token is issued fresh for every call and nothing is cached. Remove EKKA at myaccount.google.com/permissions and the very next call fails.

Turning it off

ekka gate grant list                 # find the grant
ekka gate grant revoke <grant-id>    # the next attempt is refused, live
ekka api disconnect gmail            # remove the connection from this machine

Revoking the grant stops the agent. Disconnecting removes the credential from this machine's vault. Revoking consent in your Google account stops it at the other end, and any of the three is enough on its own.