Gmail
Proxy for the Gmail v1 REST API. Gmail is fundamentally user-scoped —
every message and thread belongs to a single mailbox — so this connector
only supports idp_passthrough. Each request carries the caller's own
OAuth grant, and the policy layer adds operator-controlled guards on
outbound mail and label manipulation.
Overview
Typical uses:
- An agent reads the user's unread inbox to summarize their morning.
- A scheduling bot sends confirmation emails from the user's address.
- A compliance workflow flags and trashes phishing matches.
Service-account mode (domain-wide delegation impersonating a specific mailbox) is technically possible against Gmail but deliberately not shipped: most operator questions we've heard on agentic Gmail access are about preventing an agent from mass-emailing out of a service mailbox, not enabling it. If your use case does need SA mode, file an issue — the groundwork is already in the schema.
Default policy
The connector ships policy/gmail.rego
(package pbac.connectors.identos.google_gmail). It enforces four
things:
1. IdP-routing subject obligation
subject_obligations contains obl if {
input.resource.type == "urn:connector:identos:google-gmail"
input.connector.upstream_auth.type == "idp_passthrough"
input.subject.idp_provider != "google"
obl := { "type": "require_authn_at", "idp": "google", ... }
}
Same pattern as google-drive — callers from a non-Google session get a step-up obligation before any Gmail call succeeds.
2. Operator-enforced read-only mode
deny contains msg if {
input.resource.type == "urn:connector:identos:google-gmail"
input.action.method != "GET"
data[...].read_only == true
...
}
A single boolean at
data.pbac.operator.connectors["identos.google-gmail"].read_only
disables every mutating route: send_message, modify_labels,
trash_message, untrash_message, create_draft, create_label,
delete_label. Useful during an incident review or when piloting an
agent that should observe but not act.
3. Outbound recipient guards
Operators define two lists and the rego evaluates them on every POST:
{
"allowed_recipient_domains": ["acme.com", "partner.example"],
"blocked_recipient_domains": ["competitor.example"]
}
The denylist is checked first — any recipient matching a blocked domain
fails the request immediately. The allowlist is only enforced when
non-empty; if set, every To/Cc/Bcc address must match. The gateway
adapter populates input.resource.recipients by parsing the send /
draft payload.
4. Protected labels
{
"protected_label_ids": ["Label_42_LEGAL_HOLD"]
}
Labels listed here can't be added, removed, or deleted. Prevents an
agent from silently removing a legal-hold marker by calling
modify_labels with removeLabelIds=[Label_42_LEGAL_HOLD]. Delete on
the protected label itself is also blocked.
Supplying operator data
curl -X PUT http://localhost:8080/admin/policy/data/pbac/operator/connectors/identos.google-gmail \
-H "X-Admin-API-Key: $PBAC_ADMIN_API_KEY" \
-d '{
"read_only": false,
"allowed_recipient_domains": ["acme.com"],
"blocked_recipient_domains": ["competitor.example"],
"protected_label_ids": ["Label_42_LEGAL_HOLD"]
}'
Scope model
| Scope | Used for | |
|---|---|---|
| PBAC scopes (internal) | gmail:messages:read, gmail:messages:write, gmail:messages:send, gmail:labels:manage | Route authorization — note that send is a dedicated scope separate from write, so you can grant an agent the ability to label/archive mail without also granting it send |
| Upstream OAuth scopes | gmail.readonly, gmail.send, gmail.modify, gmail.labels | The Google-side grants the user must consent to |
See Google Workspace: multi-connector setup for how gmail's upstream scopes compose with drive and calendar on a single Google IdP.
Manifest reference
- ID:
identos.google-gmail - Version:
1.0.0 - Resource type:
urn:connector:identos:google-gmail
Supported auth modes
| Type | Details |
|---|---|
idp_passthrough | requires IdP google |
Setup fields
| ID | Label | Default | Secret? | Notes |
|---|---|---|---|---|
base_url | API base URL | https://gmail.googleapis.com | no | — |
Scopes
| Scope |
|---|
gmail:messages:read |
gmail:messages:write |
gmail:messages:send |
gmail:labels:manage |
Routes
| Method | Pattern | Scope | Resource template |
|---|---|---|---|
GET | /gmail/v1/users/me/profile | gmail:messages:read | — |
GET | /gmail/v1/users/me/messages | gmail:messages:read | — |
GET | /gmail/v1/users/me/messages/{message_id} | gmail:messages:read | gmail://message/{{message_id}} |
GET | /gmail/v1/users/me/threads | gmail:messages:read | — |
GET | /gmail/v1/users/me/threads/{thread_id} | gmail:messages:read | gmail://thread/{{thread_id}} |
POST | /gmail/v1/users/me/messages/send | gmail:messages:send | — |
POST | /gmail/v1/users/me/messages/{message_id}/modify | gmail:messages:write | gmail://message/{{message_id}} |
POST | /gmail/v1/users/me/messages/{message_id}/trash | gmail:messages:write | gmail://message/{{message_id}} |
POST | /gmail/v1/users/me/messages/{message_id}/untrash | gmail:messages:write | gmail://message/{{message_id}} |
GET | /gmail/v1/users/me/labels | gmail:messages:read | — |
POST | /gmail/v1/users/me/labels | gmail:labels:manage | — |
DELETE | /gmail/v1/users/me/labels/{label_id} | gmail:labels:manage | gmail://label/{{label_id}} |
GET | /gmail/v1/users/me/drafts | gmail:messages:read | — |
POST | /gmail/v1/users/me/drafts | gmail:messages:write | — |
MCP tools
| Name | Scope | Description |
|---|---|---|
get_profile | gmail:messages:read | Get the authenticated user's Gmail profile (email address, total messages, history ID). |
list_messages | gmail:messages:read | List messages in the authenticated user's mailbox. Supports Gmail search syntax (e.g. "from:alice newer_than:7d"). |
get_message | gmail:messages:read | Get a single message by ID. Use format=full for headers+body, format=metadata for headers only. |
list_threads | gmail:messages:read | List conversation threads. Same query syntax as list_messages. |
get_thread | gmail:messages:read | Get a single conversation thread with all its messages. |
send_message | gmail:messages:send | Send an email. Supply either a pre-built RFC 822 'raw' payload (base64url) or the structured To/Cc/Bcc/Subject/Body fields and the connector will assemble the MIME message for you. |
modify_labels | gmail:messages:write | Add or remove labels on a message (e.g. mark as read by removing UNREAD, archive by removing INBOX). |
trash_message | gmail:messages:write | Move a message to the Trash. Reversible for 30 days via untrash_message. |
untrash_message | gmail:messages:write | Restore a trashed message to the inbox. |
list_labels | gmail:messages:read | List all labels in the authenticated user's mailbox (both system labels and user-created). |
create_label | gmail:labels:manage | Create a new user label. |
delete_label | gmail:labels:manage | Delete a user-created label. System labels (INBOX, SENT, etc.) cannot be deleted. |
list_drafts | gmail:messages:read | List draft messages. |
create_draft | gmail:messages:write | Create a draft message (not sent). Same message payload shape as send_message. |
Operator data schema
Keys the operator can supply under data.pbac.operator.connectors["identos.google-gmail"].* — consumed by the connector's policy.
| Key | Type | Description |
|---|---|---|
read_only | boolean | Global kill-switch for all mutating routes (send, modify, trash, drafts, labels). When true, only read operations are allowed. |
allowed_recipient_domains | array | Outbound email domains the caller is allowed to send to (e.g. 'acme.com'). Empty means no restriction. If set, every To/Cc/Bcc recipient must match. |
blocked_recipient_domains | array | Outbound email domains that must never receive mail (evaluated before allowed_recipient_domains; easier to manage a small denylist than a large allowlist). |
protected_label_ids | array | System/user label IDs that modify_labels and delete_label must not touch. Useful for regulatory holds (LEGAL, LITIGATION_HOLD, etc.). |