gdoc-email-drafts
Put draft emails in a Google Doc as email blocks. A person clicks a block's Gmail icon to open it as a filled-in Gmail draft. Use for any Doc of emails a human will send.
SKILL.mdskills/gdoc-email-drafts/SKILL.md
GitHubDraft emails in a Google Doc
When a person will send the emails, they belong in a Doc as email blocks. Each block is the To / Cc / Bcc / Subject / Body table that Docs shows with a Gmail icon in the right margin. Hover the block, click the icon, and Gmail opens a draft with every field filled in. ACE writes the drafts, and the person reviews them and sends them.
A bold Subject: line followed by a quoted body is not a draft email. It looks like one,
but clicking it does nothing. Always use the block.
Where the procedure lives
This is fleet-wide, and canopy owns it: agent-core/email-drafts.md in the installed canopy
plugin. Eva and every other agent use it through canopy gdoc email-blocks, which runs as the
agent's own Google account. Read that doc for the rules. This skill only binds them to ACE.
ACE's tool, and why it is not the canopy command
ACE uses docs_insert_email_blocks (ace-gdrive; exact signature in docs/atom-schemas.md), not
the canopy CLI, for two reasons:
- ACE's docs are created and owned by its Drive service account, and ace@ itself gets
403 PERMISSION_DENIEDon them (measured 2026-09-24), so the canopy command cannot edit them. marketplace-outreach-docruns in a confined panel turn with no shell, so a CLI is out of reach there anyway.
The two are one algorithm in two languages: lib/docs-email-block.ts here and canopy's
src/orchestrator/gdoc_email_blocks.py. Change them together. Do not call chrome-sales'
docs_insert_email_block from ACE: it runs as a third service account, which needs writer
access on every doc, and it leaves the index math to you.
Procedure
Write the doc with an anchor wherever a block goes. Build the markdown for
drive_create_doc_from_markdown(on a Shared Drive: a run folder, or a find-or-create folder underACE_DRIVE_ROOT_FOLDER_ID— a value in ACE's Canopy vault, in the installed.env— when there is no opportunity). Put each email's heading and context first. Then, alone on its own line with a blank line either side, write the anchor@@EMAIL_<key>@@. The key may use letters, digits,_and-, and each key appears once. Do not write the email text in the markdown; the block carries it.## Acme Health — invitation @@EMAIL_acme@@Never put the anchor on a heading line. The block would take the heading's 18pt font, and the tool refuses it for that reason.
Call
docs_insert_email_blocksonce, with every block:documentIdplusblocks: [{anchor, to, cc?, bcc?, subject, body}]. Inbody,\nseparates paragraphs. When no address is known, leavetoempty or put a visible placeholder like[Name]. Never guess an address.The tool checks every anchor before it writes anything. It fails, and changes nothing, when an anchor is missing, duplicated, or on a heading. It then inserts the blocks last-first and deletes the anchor tokens it used. Any
@@EMAIL_*@@token that no block asked for comes back inunusedAnchorsand stays in the doc, where a reader will see it.Insert the blocks LAST. Re-publishing markdown over the doc with
drive_create_doc_from_markdownordrive_update_filerewrites the whole body and wipes the blocks. To change one email afterwards, rebuild the doc: publish the markdown, then run step 2 again.Share the doc as the procedure you came from says. Lead with the run's ace-web page when the doc belongs to a run (CLAUDE.md § Conventions), and reply with the link and a one-line count. Do not paste the emails into the reply; they are in the doc.
What this skill does not do
- Send. The Gmail icon is the send step, and it belongs to a person. ACE's own send path is [[email-communicator]], which is approval-gated and meant for ACE's own correspondence, not for drafts a human sends.
- Add a margin icon to anything but a block. A plain link, a bold subject line or a quoted body gets no icon.
Validating a change to the helper before the MCP restarts
Compiled MCP code only takes effect after a full Claude restart. To exercise
lib/docs-email-block.ts against a live doc before that, run the script below. It uses the
same code as the atom and reads ACE's SA key from the plugin-data dir (or from
GOOGLE_APPLICATION_CREDENTIALS).
ACE_ROOT="${CLAUDE_PLUGIN_ROOT:-$(python3 -c "import json,os; d=json.load(open(os.path.expanduser('~/.claude/plugins/installed_plugins.json'))); print(d['plugins']['ace@ace'][0]['installPath'])")}"
node "$ACE_ROOT/node_modules/tsx/dist/cli.mjs" "$ACE_ROOT/scripts/run-email-blocks.ts" <documentId> <blocks.json>
Terminology
The platform is Connect, never CommCare Connect, in every email drafted this way. The
full rule, including the carve-outs for CommCare the mobile app, CommCare HQ and identifiers,
is skills/_terminology.md.
Used by: [[marketplace-outreach-doc]]. Related: eva:email-macros (the same result through
chrome-sales), eva:gdoc-review (a visual check worth running before a link goes out).