Gmail attachment
A collection of practical Claude Code skills — multi-LLM evaluation, domain management, planning, writing quality, and pair-session patterns. MIT licensed.
npx -y skills add eprouveze/claude-skills --skill gmail-attachmentAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Send (or draft) a Gmail message with one or more file attachments via the google-workspace MCP. Use whenever the user asks to email a file, attach a document, send a PDF / markdown / spreadsheet by mail. Trigger phrases — "email this file to X", "send by mail with the doc attached", "envoie ce fichier par mail", "joindre en pièce jointe". The MCP's documented `path` field is sandboxed and silently fails on host paths; this skill enforces the only working route (base64 `content` + `filename`) and documents the size envelope.
SKILL.md
10.1 KB, as published. Nobody here has run it
gmail-attachment — Send Gmail with attachments via google-workspace MCP
Why this skill exists
The google-workspace MCP runs in its own filesystem sandbox (typically
/app/app/external/servers/). The attachments parameter accepts two shapes:
| Shape | Status | Why |
|---|---|---|
{path: "/Users/.../file.pdf"} | ❌ Always fails | The MCP looks for the path inside its sandbox. Host paths don't exist there. The error message ("Path does not exist") is misleading because the path does exist — just not where the MCP can see it. Copying to /tmp doesn't help — /tmp isn't shared either. Project-relative paths fail too. |
{content: "<base64>", filename: "..."} | ✅ Works | Content is sent inline; the MCP doesn't need to read host disk. |
Without this skill, the natural reflex is to pass path (it's the documented field). It
always fails, and the failure mode is misleading. This skill enforces the working route
and documents the practical size envelope so you don't paste 80 KB of base64 into a tool
call and watch it get truncated.
When to use
Trigger on any request that involves emailing a file:
- "email this PDF to [email protected]"
- "envoie ce fichier par mail à Y"
- "send the report as an attachment"
- "draft an email with the doc attached"
Use this skill before reaching for mcp__google-workspace__send_gmail_message or
mcp__google-workspace__draft_gmail_message directly when an attachment is involved.
Do NOT use for: emails without attachments, attaching Drive files (use sharing instead), attaching content the user pasted inline (no file involved).
The procedure
1. Confirm intent
Before sending, confirm:
- Recipient address(es)
- Subject and body (or have the user provide them)
- File(s) to attach — get absolute host path(s)
from_nameif the message should appear from a persona
For irreversible actions (send vs draft), default to draft unless the user explicitly said "send". Show the prepared call before executing.
2. Encode each file to base64
base64 -i "<absolute-host-path>" | tr -d '\n' > /tmp/__attach_<slug>.b64
wc -c /tmp/__attach_<slug>.b64 # sanity-check size
tr -d '\n' strips the line wrapping that base64 adds by default — Gmail rejects
wrapped base64 inside JSON.
3. Read the encoded content into context
Use the Read tool on the .b64 file. The Read tool returns the full content on one line
with a 1\t line-number prefix that you must strip (the actual content starts after the
tab character).
Size envelope. The base64 is ~1.37× the original size. The reliable ceiling is much lower than you'd think — empirically, a 34.5 KB encoded payload timed out mid-call ("API Error: The operation timed out"). Plan conservatively.
| Original file size | Encoded size | Recommendation |
|---|---|---|
| ≤ 15 KB | ≤ 20 KB | Inline content works reliably |
| 15–30 KB | 20–40 KB | Inline content may time out — verify by checking sent mail |
| > 30 KB | > 40 KB | Don't try inline. Use the draft-and-attach-manually escape hatch (below) or share via Drive |
Escape hatch — compose manually in Gmail web UI
When the file is too large for reliable inline send:
- Hand the user the prepared message: To, Subject, Body, attachment host path.
- Tell them to compose in Gmail web UI and attach the file there.
- Done. Costs ~30 seconds, avoids both the timeout and the truncation trap.
Note. The current
google-workspaceMCP exposes onlymcp__google-workspace__send_gmail_message— there is nodraft_gmail_message. So the previously-imagined "MCP-draft + manual attach" route does not work; full-manual compose is the actual escape hatch. Verified by attemptingdraft_gmail_messageand getting "No such tool available".
This is often the right choice even when inline would work, because it keeps the human in the loop on the irreversible action (the actual send).
4. Build and execute the MCP call
mcp__google-workspace__send_gmail_message(
to = "...",
subject = "...",
body = "...",
from_name = "...", # optional, persona
attachments = [{
"filename": "<original-filename-with-extension>",
"content": "<full base64 string, no line-number prefix, no wrapping>",
"mime_type": "<correct mime>" # see table below
}]
)
No drafts in this MCP. The current
google-workspaceMCP version exposes onlysend_gmail_message. If you want a draft, use the manual-compose escape hatch above (the user composes in Gmail web UI directly).
5. Verify
After execution, check the returned message ID. If sending a corrected version of an earlier message, mention that the previous one should be ignored. For high-stakes sends, search sent mail to confirm delivery and, where possible, ask the recipient to confirm the attachment opened.
MIME type cheat sheet
| Extension | MIME |
|---|---|
.pdf | application/pdf |
.md | text/markdown |
.txt | text/plain |
.docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
.xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
.pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation |
.csv | text/csv |
.png | image/png |
.jpg / .jpeg | image/jpeg |
.zip | application/zip |
| (unknown) | application/octet-stream |
Multi-file pattern
Encode each file separately, then combine into the array:
attachments = [
{"filename": "report.pdf", "content": "<b64-1>", "mime_type": "application/pdf"},
{"filename": "data.csv", "content": "<b64-2>", "mime_type": "text/csv"},
]
Don't zip silently — the user expects discrete files.
Known gotchas
- The
pathfield is a trap. It is documented in the MCP schema but always fails for host files. Treat it as nonexistent. base64macOS default wraps lines at 76 chars. Withouttr -d '\n', the JSON-side base64 is invalid and the Gmail API rejects the attachment. Always strip newlines.- Read tool prefix. The tool prepends
<line-number>\tto every line of file output. When you copy the content into a tool call, you must drop that prefix. Including it produces invalid base64 and the email gets sent with a corrupt attachment — silently. - Tool-call truncation by you, not the MCP. Above ~80 KB of inline content, the model is tempted to "summarize" or "truncate for safety". This is the most common failure mode. If the original file is large, draft rather than send, or share via Drive.
Anti-patterns
- ❌ Falling back to
pathafter acontentfailure. The MCP fails again the same way; you've just doubled the spend. - ❌ Pasting "<full base64 here, ~26 KB, omitted>" or any other placeholder into the
contentfield. The recipient receives a truncated/garbled file. This actually happened in early use — the cost is a follow-up "correction" email and a small loss of credibility. - ❌ Using URL-safe base64 (
-_alphabet). The MCP expects standard base64.base64 -ion macOS produces standard. - ❌ Mixing
pathandcontentin the same attachment object. Pick one (alwayscontent). - ❌ Treating
osascriptexittrueas "delivered". AppleScript Mail.app returnstruefrom a successfulsendcall, but that only means the script ran — the message may sit in Outbox indefinitely if the account auth is stale, the network is offline, or some other silent Mail.app condition fires. Verify by checking sent mail (or asking the user) before claiming success. Same general principle applies to any fire-and-forget send mechanism. - ❌ Sending from a personal account out of a work environment. When operating in a work session (e.g., Salesforce-managed laptop), do not propose AppleScript Mail.app routes that would send via a personal iCloud / Gmail / etc. account. Even when the recipient is the operator themselves, mixing personal-account egress with work-context content is a mandate-vs-campagne-style boundary violation. If the only Mail.app account configured is personal, that route is disqualified for work content — fall back to the manual-compose escape hatch in the work webmail.
Validated patterns
- Encode → Read → Inline
content. Verified via a successful send of a ~26 KB Markdown file that returned a Gmail message ID and arrived intact. - Failure recovery flow when the first send went out with a truncated attachment: send a brief follow-up titled "Correction — <original subject>", open with one line asking the recipient to ignore the previous mail, attach the correct file via this skill.
- Persona sending via
from_name = "Lex (pour Emmanuel Prouveze)"works as expected — the recipient sees the persona, replies route to the authenticated account.
Self-improvement
Append observations to learnings.md whenever:
- A new MIME type is encountered
- The size envelope shifts (different model, different runtime)
- A new failure mode appears (e.g., unicode filenames, multi-recipient quirks)
- The MCP schema changes
Consolidate weekly, or when learnings.md crosses ~100 bullets. On consolidation, each
entry gets one of: apply (promote into the SKILL body), capture (keep in the
log), dismiss.
See also
- For Drive sharing instead of attaching:
mcp__google-workspace__manage_drive_access. - For inline images in HTML body: not currently supported by the
google-workspaceMCP. Use a Drive link.