gws-gmail-send ยท diff
git:20260731.752afe3 to git:20260928.1f81021
12 added, 93 removed. Audit A to A.
---
name: gws-gmail-send
description: Use when drafting or explicitly sending a Gmail message through gws for a selected isolated account alias.
---
# Draft or send Gmail
- **REQUIRED:** Apply [gws-shared](../gws-shared/SKILL.md) first. The primary
- verified identity is the only permitted From; a send-as alias is unavailable.
-
- ## Authoritative draft preview
-
- Always create a server-side draft first with
- `"$gws_bin" gmail +send --from "$expected_email" --to <recipients> --subject <subject> --body <body> --draft`
- in the exact isolated environment. Parse exactly one draft ID from the helper's
- JSON result; zero, duplicate, or malformed IDs fail closed. Fetch that ID with
- raw `users.drafts.get` using the `full` format.
-
- The `gws v0.22.5 schema` defines `gmail.users.drafts.get` with required
- `userId` and `id` parameters plus `format=full`, and
- `gmail.users.drafts.send` with required `userId` parameters plus a `Draft`
- request body. After shared preflight, use this exact isolated runner and
- JSON-encode the parsed ID rather than interpolating it:
-
- ```bash
- isolated_gws() (
- cd / || exit 1
- /usr/bin/env -u GOOGLE_WORKSPACE_CLI_TOKEN \
- -u GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE \
- -u GOOGLE_WORKSPACE_CLI_CREDENTIAL_FILE \
- -u GOOGLE_WORKSPACE_CLI_CLIENT_ID \
- -u GOOGLE_WORKSPACE_CLI_CLIENT_SECRET \
- -u GOOGLE_WORKSPACE_CLI_LOG \
- -u GOOGLE_WORKSPACE_CLI_LOG_FILE \
- -u GOOGLE_WORKSPACE_PROJECT_ID \
- -u GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE \
- -u GOOGLE_WORKSPACE_CLI_SANITIZE_MODE \
- -u GOOGLE_APPLICATION_CREDENTIALS \
- GOOGLE_WORKSPACE_CLI_CONFIG_DIR="$profile" \
- GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file \
- GOOGLE_APPLICATION_CREDENTIALS="$profile/missing-adc.json" \
- "$gws_bin" "$@"
- )
-
- draft_get_params="$(
- DRAFT_ID="$draft_id" /usr/bin/python3 -I - <<'PY'
- import json
- import os
- print(json.dumps({
- "userId": "me",
- "id": os.environ["DRAFT_ID"],
- "format": "full",
- }, separators=(",", ":")))
- PY
- )" || exit 1
- draft_json="$(isolated_gws gmail users drafts get --params "$draft_get_params")" || exit 1
- ```
-
- Treat the full draft readback as authoritative. Validate the actual From
- case-insensitively against `$expected_email`; validate actual To/CC/BCC,
- subject, new-message thread context, and attachment names and count against the
- request and staged inputs. Recursively base64url-decode every inline
- `text/plain` and `text/html` MIME leaf in `draft.message.payload`; preserve the
- API part order and build a canonical MIME content digest from each part path,
- lowercase MIME type, decoded byte length, and SHA-256 of its decoded bytes.
- Validate decoded body content against the requested body and any expected
- helper-generated MIME structure. Missing or undecodable body bytes fail closed.
- Preview the decoded body content as non-executable text plus the canonical MIME
- content digest in the identity/recipient preview; never render active HTML.
- Preview the readback, including draft state. A draft-only request stops after
- this preview.
+ **REQUIRED:** Apply [gws-shared](../gws-shared/SKILL.md) and read its
+ [compose and attachment contract](../gws-shared/references/compose.md) before
+ any operation. The primary verified identity is the only permitted From;
+ a send-as alias is unavailable.
- ## Optional send-now boundary
+ Validate new-message thread context against the request.
- Only explicit user intent to send now is pre-authorization. Immediately before
- sending, perform another full `users.drafts.get` and require an immediate
- unchanged readback of the exact newly created draft, including From, To/CC/BCC,
- subject, thread context, attachment names and count, decoded body bytes and
- canonical MIME content digest. Then, and only then, encode that same ID as the
- entire `Draft` request body and invoke the exact isolated raw send:
+ Always create a server-side draft first:
```bash
- draft_json_again="$(isolated_gws gmail users drafts get --params "$draft_get_params")" || exit 1
- draft_send_body="$(
- DRAFT_ID="$draft_id" /usr/bin/python3 -I - <<'PY'
- import json
- import os
- print(json.dumps({"id": os.environ["DRAFT_ID"]}, separators=(",", ":")))
- PY
- )" || exit 1
- isolated_gws gmail users drafts send --params '{"userId":"me"}' --json "$draft_send_body" || exit 1
+ /bin/bash "$gws_account" run --alias "$gws_alias" --expected-email "$expected_email" -- gmail +send --from "$expected_email" --to <recipients> --subject <subject> --body <body> --draft
```
- Any mismatch, deadline, prior draft, or ambiguous request must fail closed;
- never rebuild or send with `users.messages.send`.
-
- ## Attachment staging
-
- Apply the shared attachment safety contract independently to each attachment:
- perform the initial lstat, canonical device/inode, SHA-256, and byte size
- record; create a private temporary directory; copy the exact bytes to a
- mode-`600` file with the same basename; perform the post-copy original restat
- and rehash; and require the staged digest to match. Preview the original
- absolute path, basename, size and digest. Perform the final staged digest check,
- pass only the staged copy to gws, and cleanup on every exit. Never pass the
- mutable user-supplied path.
+ Follow the mandatory reference for the full authoritative readback, identity/recipient
+ preview, attachment staging, and decoded MIME validation. A draft-only request
+ stops after preview. Only explicit user intent to send now permits the reference's
+ exact newly created draft send after immediate unchanged readback; never rebuild
+ or use `users.messages.send`. Fail closed on any mismatch or uncertain outcome.