Email
Read mail with the Spark CLI, and make every change to mail through the Gmail connector, except a draft the user asks to have in Spark. Leave what to do with an email, and when, to the user. For calendar events and free time, use
.
When to read mail
List, search or read mail, through either tool, only when the user asks for an email task that needs those reads. The mailbox is private, so reading it is the user's call, not the agent's.
- When mail would only help another task, such as a coding task that a thread might explain, or when a project or skill instruction says to check mail: say what you would look up and why, and read only after the user agrees.
- When the task changes: the user's request covers the email task it was for. Ask again before reading mail for an unrelated task.
Drafts
- When the user asks for a draft without naming Gmail or Spark, such as "draft a mail to the landlord": write the text in chat. Read mail for it only when the request points at mail it needs, such as the email being answered, and ask before any other lookup.
- When the user asks for the draft in Gmail or in Spark, in any wording ("save it as a Gmail draft", "put it in my Spark drafts"): save it there, with that tool only. Naming a tool allows that draft, not other changes to mail.
- When the named tool can't do it, such as refused because Spark's access level is read-only: say so, and keep the draft in chat. Don't switch to the other tool unasked.
Every create, update or delete of a saved draft needs the user's own approval in the harness, even after they asked for the draft: a saved draft is a change in their mailbox, and one mistaken call can overwrite a draft they were writing.
's ask rows
and
make each of these calls ask.
- When the harness won't ask the user themselves, keep the draft in chat and say why. That covers Claude Code's , and modes and a non-interactive run, Codex with an automatic reviewer, Cursor and opencode in a mode that runs tools without asking, and any harness where the call doesn't ask. An automatic reviewer's approval is not the user's.
Safety
The user reviews and sends every email themselves, so go no further than a draft.
Within an email task, archive an email when the task calls for it. Make any other change only after the user asks for that exact change, or after you name it and they agree: a pin, a label, an undo, or a saved draft as above.
Gmail tools
| Tools | Use |
|---|
| , , , , , | For an email task the user asked for. |
| or removing | Archive freely within that task. |
| , and for any other label, , | After the user asks, or agrees when you name it. Never add or , since that trashes or spams the email around the deny rules. |
| , , | When the user asks for a Gmail draft, and each call approved by the user in the harness. |
| , | After the user asks, or agrees when you name it. |
| Only when the user names that label to delete, since it removes the label from every email. |
Claude Code's permission rules deny the rest, and a denied tool doesn't show up to be called:
- , which also sends existing drafts;
- and ;
- and ;
- and ;
apply_sensitive_message_label
, apply_sensitive_thread_label
and batch_apply_sensitive_thread_labels
, which can trash or spam.
Google's permission that allows archiving also allows sending, so only those deny rules stop a send.
- When a denied tool can be called: the setup is incomplete. Tell the user, never call it, and make no change to mail until the deny rules are fixed.
The two tools
| Tool | Use it for | Speed and size | Needs |
|---|
| Spark CLI, | Listing, searching and reading mail; Spark's categories, pins and contacts | About 0.1 s, compact tables | Spark Desktop running. Mail access is read-only on the free level. |
| Gmail connector, | Every change to mail but a Spark draft | About 1 s, JSON three to four times Spark's size | The connector signed in |
Spark reads Spark Desktop's local copy of the mail. The Gmail connector is Google's Gmail MCP server,
, connected as a Claude connector.
How Spark's view maps to Gmail
| In Spark | In Gmail |
|---|
| Done | The label removed, which archives the email. Spark shows it within about a minute. |
| Pin | The label. Spark's and return the same emails. |
| A folder | A Gmail label. |
| A category: priority, personal, invitation, notification, newsletter | Spark's own; no Gmail tool changes it. |
| Today, Last week, a month | Each email's date. |
Flow
When setting either tool up, checking the deny and ask rules, or when a call fails on sign-in or permission, follow
.
-
Find the mail with Spark.
bash
spark emails Inbox --filter "category:priority is:unread" # one Spark category
spark emails --filter "is:pinned" # pinned mail
spark search --filter 'from:<sender> newer_than:7d' # any Gmail-style query
spark thread <id> # one email in full
spark contacts --help # search contacts
List with
or
spark search --filter '<gmail-style query>'
.
returns the full bodies of up to 20 matches, often tens of thousands of tokens, so use it only when you need those bodies.
- When you need a command's flags: run . prints the full reference, about 13,000 tokens, so reach for it only when is not enough.
-
Find it again in Gmail before changing it, because the two tools number messages differently. Search with
, using
and
from the Spark row. Pass the view
THREAD_VIEW_METADATA_ONLY
when only the IDs are needed, since it keeps the answer small.
- When more than one thread matches: narrow the search with and around the Spark row's date, or ask the user which one they mean.
-
Act through Gmail: archive, or make the change the user asked for.
| Change | Calls |
|---|
| Mark done | Add to the search from step 2, then with . with puts it back. |
| Pin or unpin | or with . |
| Label | for the label's ID, when it does not exist yet, then with that ID. |
| Save a reply as a Gmail draft | Read the thread with , then with set to the last message's . |
Call
with
messageFormat: PLAIN_TEXT
, since it keeps the thread small.
- For any change but archiving that the user hasn't asked for: name it and wait for their answer.
- For a saved draft: end with its , where the user reviews and sends it.
- For a draft the user asked to have in Spark: run as a command of its own, so the user approves that call alone, and tell the user where Spark shows the draft.
-
Check that each change took.
- After marking done: the search returns once the email is archived.
References
- setup.md: setting up Spark and the Gmail connector, the deny and ask rules, and checking each part.