Gmail ExecModule
Overview
GmailModule connects ValkyrAI workflows to Gmail API v1 for messages, threads, labels, recoverable trash actions, and drafts. It exposes 13 bounded operations through the native map I/O ExecModule ABI and keeps the Google OAuth access token inside a READY IntegrationAccount.
The module always addresses the authenticated user as me, fixes the production host to https://gmail.googleapis.com/gmail/v1/users/me, caps encoded requests at 1 MiB and responses at 5 MiB, returns one provider page per execution, retries transient failures only for safe GET operations, and never automatically retries sends, label changes, trash actions, or draft mutations.
The official n8n Gmail node informed the high-value operation set: read and list messages, work with threads and labels, send mail, and manage drafts. ValkyrAI adds the IntegrationAccount security boundary, fixed-host routing, explicit bounds, response identity checks, mutation non-retry, and credential-redacted errors. This Gmail API connector does not duplicate the generic EmailModule, which is an IMAP/SMTP transport rather than a Gmail mailbox API.
Usage
- Enable Gmail API in the Google Cloud project that owns the OAuth client.
- Authorize only the scopes required by the chosen operations.
- Store the short-lived Google OAuth access token in the encrypted
apiKeyorpasswordfield of a ValkyrAIIntegrationAccount; set the account toREADY. - Bind that account through
ExecModuleConfig.authConfig.integrationAccount. - Choose one operation and provide its conditional fields.
- When a list returns
hasMore: true, passnextPageTokenunchanged to a later execution.
The module does not refresh OAuth tokens. The account lifecycle or integration service must rotate the stored access token before it expires.
Inputs
| Name | Type | Required | Description | Constraints |
|---|---|---|---|---|
operation | string | Yes | Operation listed below. | Exact allowlist of 13 values. |
resourceId | string | Conditional | Gmail message, thread, or draft ID. | 1–256 URL-safe provider-ID characters. |
message | object | Conditional | Email fields for send_message and create_draft. | Non-empty, at most 8 fields, encoded request at most 1 MiB. |
label | object | Conditional | Label name and visibility for create_label. | Non-empty, at most 3 allowlisted fields. |
labelIds | array | No | Labels that all listed messages or threads must have. | At most 20 unique provider label IDs. |
addLabelIds | array | Conditional | Labels added by modify_message. | At most 100 unique IDs. |
removeLabelIds | array | Conditional | Labels removed by modify_message. | At most 100 unique IDs. |
query | string | No | Gmail search-box query for message or thread lists. | At most 4,096 characters; control characters rejected. |
pageToken | string | No | Opaque provider continuation. | 1–2,048 bounded token characters; passed unchanged. |
limit | integer | No | Provider page size. | 1–500; default 100. |
format | string | No | Single message/thread representation. | minimal, metadata, or full; messages also support raw; default metadata. |
includeSpamTrash | boolean | No | Include SPAM and TRASH in searchable lists. | Default false. |
message fields:
| Field | Required | Description |
|---|---|---|
to | Yes | One email address, a comma-separated string, or an array; at least one recipient. |
cc, bcc | No | Additional recipient strings or arrays; at most 100 total recipients across all fields. |
replyTo | No | Valid reply-to email address. |
subject | Yes | Non-empty subject, at most 512 characters with no header controls. |
body | Yes | Plain text or HTML body, at most 512 KiB before encoding. |
bodyType | No | text or html; default text. |
threadId | No | Existing Gmail thread ID when intentionally adding a message to a thread. |
The module constructs RFC 2822 MIME itself, encodes the subject and body as UTF-8, and sends the resulting message as base64url in Gmail’s raw field. Attachments, arbitrary headers, and caller-provided raw MIME are deliberately excluded from this version.
Outputs
| Name | Type | When present | Description |
|---|---|---|---|
status | string | Always | success or error. |
operation | string | Always | Normalized operation. |
resource | string | After validation | messages, threads, labels, or drafts. |
data | object | Single-resource success | Provider response after a valid ID is verified. |
id | string | Single-resource success | Verified Gmail resource identity. |
items | array | List success | One bounded provider page. |
count | integer | List success | Items returned in the page. |
resultSizeEstimate | integer | When supplied by Gmail | Provider estimate for searchable lists. |
hasMore | boolean | List success | Whether Gmail supplied a continuation. |
nextPageToken | string | When another page exists | Opaque token for the next execution. |
httpStatus | integer | Provider response | Gmail HTTP status. |
attempts | integer | Provider call or error | HTTP attempts consumed. |
error | object | Failure | Redacted code, message, and retryable fields. |
Mailbox responses are classified restricted. OAuth tokens, MIME bodies, recipients, and provider payloads are not copied into progress logs.
IntegrationAccount Requirements
| Setting | Requirement |
|---|---|
| Provider | Gmail API v1 |
| Authentication | OAuth 2.0 bearer access token |
| Status | Exactly READY |
accountName | Human-readable Google Workspace identity |
apiKey or password | Encrypted OAuth access token, 20–4,096 non-whitespace characters |
| Relationship | ExecModuleConfig.authConfig.integrationAccount |
Recommended scopes:
- Read-only message/thread workflows:
https://www.googleapis.com/auth/gmail.readonly. - Labels, trash, and mailbox modification:
https://www.googleapis.com/auth/gmail.modify. - Sending and drafts:
https://www.googleapis.com/auth/gmail.composeor the narrower send scope where it covers the selected action. - Full mailbox scope
https://mail.google.com/should be avoided unless a separately reviewed workflow truly requires it.
Google classifies several Gmail scopes as sensitive or restricted. Production OAuth verification, consent, token refresh, and domain-wide delegation remain outside this module. The fixed me path intentionally prevents caller-selected user impersonation.
Configuration
Illustrative persisted configuration:
{
"version": "1.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:gmail-customer-success"
},
"payloadConfig": {
"parameters": "{\"operation\":\"list_messages\",\"limit\":100,\"query\":\"in:inbox is:unread\",\"labelIds\":[\"INBOX\"]}"
}
}
The relationship is symbolic. The generated IntegrationAccount record owns the encrypted OAuth token.
Operations
| Resource | Operation | Provider route | Behavior |
|---|---|---|---|
| Messages | list_messages | GET /messages | One page with query, label, spam/trash, and continuation filters. |
| Messages | get_message | GET /messages/{id} | Gets one message in the selected format. |
| Messages | send_message | POST /messages/send | Sends one module-built RFC 2822 message. |
| Messages | modify_message | POST /messages/{id}/modify | Adds and/or removes labels. |
| Messages | trash_message | POST /messages/{id}/trash | Moves one message to recoverable trash. |
| Messages | untrash_message | POST /messages/{id}/untrash | Restores one message from trash. |
| Threads | list_threads | GET /threads | One searchable thread page. |
| Threads | get_thread | GET /threads/{id} | Gets one thread. |
| Labels | list_labels | GET /labels | Lists system and user labels. |
| Labels | create_label | POST /labels | Creates one user label with allowlisted visibility fields. |
| Drafts | list_drafts | GET /drafts | One draft page. |
| Drafts | create_draft | POST /drafts | Creates one RFC 2822 draft. |
| Drafts | send_draft | POST /drafts/send | Sends one existing draft by ID. |
Permanent message deletion, draft deletion/update, label deletion/update, attachments, replies that first hydrate an original message, batch mutation, mailbox history, watches/triggers, settings, filters, aliases, delegated-user access, and raw MIME passthrough are deferred.
Errors and Failure Modes
| Failure | Cause | Retry guidance |
|---|---|---|
VALIDATION_ERROR | Missing or malformed field, unsafe header/recipient, unsupported label/format, invalid ID/token, or local size/page limit. | Correct the request; no provider call was sent. |
INTEGRATION_ACCOUNT_REQUIRED | No bound secure account. | Bind the intended Google OAuth account. |
INTEGRATION_ACCOUNT_NOT_READY | Account status is not READY. | Refresh or repair the account before execution. |
GMAIL_HTTP_400/404 | Provider validation, invalid Gmail query, or missing resource. | Correct the request or reconcile the provider ID. |
GMAIL_HTTP_401/403 | Expired token, disabled API, missing scope, Workspace policy, or insufficient consent. | Refresh authorization and grant only the required scope. |
GMAIL_HTTP_429 | Per-user, per-project, or quota pressure. | Reads retry up to three total attempts; writes remain single-attempt. |
GMAIL_HTTP_5xx / NETWORK_ERROR | Provider or transport failure. | Reads retry safely. Reconcile sends, label changes, trash actions, and drafts before manual retry. |
RESPONSE_TOO_LARGE | Provider response exceeded 5 MiB. | Use metadata/minimal, narrower searches, or a smaller page. |
| Invalid success envelope | Gmail returned no valid resource ID, the wrong ID, or a non-object response. | Treat as failure and verify provider compatibility. |
Errors are bounded to 500 characters and pass through credential redaction. A timed-out write may have reached Gmail, so the module refuses automatic mutation replay.
Example
Send one approved customer update:
{
"operation": "send_message",
"message": {
"to": ["buyer@example.com"],
"cc": ["account-owner@example.com"],
"replyTo": "support@example.com",
"subject": "Your account review is complete",
"body": "Your review is complete. Reply to this email if you need help.",
"bodyType": "text"
}
}
Expected normalized result:
{
"status": "success",
"operation": "send_message",
"resource": "messages",
"id": "18ca01f8b7",
"data": {
"id": "18ca01f8b7",
"threadId": "18ca01f8b7",
"labelIds": ["SENT"]
},
"httpStatus": 200,
"attempts": 1
}
Notes
- Pagination: each execution returns at most one page. Reuse
nextPageTokenverbatim; no silent all-pages loop occurs. - Rate limits: Gmail uses per-user and per-project quotas. The module handles
429and transient failures with at most three attempts forGETonly and bounded numericRetry-Afterhandling. - API limits: provider page size is capped at 500, module requests at 1 MiB, responses at 5 MiB, search at 4,096 characters, list labels at 20, modifications at 100 labels, and outgoing recipients at 100.
- Idempotency: reads are retry-safe. Sends, draft operations, label mutations, and trash actions are single-attempt because a timeout can leave completion ambiguous.
- Destructive behavior: permanent deletion is not exposed.
trash_messageis recoverable withuntrash_message; sending a draft removes the draft and creates a sent message. - Gmail search:
queryuses Gmail search-box syntax. Some searches are unavailable to tokens authorized only with the metadata scope. - Message bodies: this version emits a single UTF-8 plain-text or HTML MIME part. It intentionally omits attachments and arbitrary caller headers.
- External verification: deterministic tests cover validation, fixed routing, query construction, MIME encoding, bearer authentication, pagination, retries, mutation non-retry, redaction, identity checks, and annotation discovery. Live Gmail execution is deferred until a separately authorized Google account is supplied.
See the official Gmail message resource, Gmail labels resource, Gmail drafts resource, and n8n Gmail message operations for upstream functional references.