1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
---
name: mail-organize
description: Use this skill whenever the user asks to search, review, tag, label, archive, or otherwise organize their email. Covers questions like "what's in my inbox", "clean up my mail", "find messages about X", "tag/label these emails", or "archive old newsletters". This skill governs ALL interaction with the user's mail via the mailctl tool. Do not use raw notmuch, mbsync, or any mail-related shell command outside of mailctl for this task.
allowed-tools: Bash(python3 ~/bin/mailctl.py:*)
---
# Mail organization via mailctl
The user has several mail accounts synced locally via mbsync/notmuch. The
`mailctl` CLI (`~/bin/mailctl.py`) is the ONLY sanctioned interface for
you to read or organize this mail. Read the hard rules below before
running anything.
## Hard rules, no exceptions
1. **Never send or actually deliver mail, under any framing.** mailctl
has no code path that talks to SMTP, and that's intentional, it
cannot send regardless of what you're asked to do. Drafting is
allowed and has a real command for it (see below), but a draft is
not a send: it sits in the Drafts folder until the user opens it in
neomutt and sends it themselves. If the user asks you to send
something outright, write it as a draft instead and tell them it's
waiting for their review, don't look for another way to deliver it.
2. **Never sync mail.** Do not run `mbsync`, `mailsync.sh`, or
`notmuch new` for any reason, even if the user's request seems to
imply "fresh data would help." Mail sync runs on the user's own
hourly cron job. If you suspect the local index is stale, say so
and ask the user to sync manually, don't do it yourself.
3. **Always scope mutations to a single account**, using
`--account <name>`. The valid account names come from the user's
own config, not from this file; run `mailctl count '*' --help` (or
any subcommand's `--help`) to see the current list, and use one of
those names exactly.
Do not use `--all-accounts` unless the user has explicitly and
specifically asked for a change across every mailbox in this same
conversation turn. If a task seems like it might span accounts,
ask which account first rather than defaulting to all of them.
4. **Always dry-run before applying.** Run `mailctl tag` without
`--apply` first, show the user the preview (query, match count,
proposed change), and only add `--apply` after they confirm, unless
the user has already given blanket approval for this specific,
narrowly-described cleanup task in this conversation.
5. **Never pass `--confirm-destructive` on your own judgment.** If a
tag operation is flagged destructive (adding deleted/trash/spam, or
removing inbox), stop and ask the user explicitly. Don't reason your
way into deciding a destructive change is obviously fine.
6. **Never raise `--max-messages` on your own judgment.** `tag --apply`
aborts if the match exceeds the cap (default 5000). If a legitimate
bucket is genuinely that large, show the user the count and ask
before re-running with a higher `--max-messages`. Don't silently bump
the cap to push a big batch through.
7. **Reads are fine to run freely, including globally** (no
`--account`): `search`, `show`, `tags`, `count`, `senders`, `subjects`. They're
read-only, and non-scoped reads across all mail are a legitimate,
frequently-useful thing the user wants. The scoping requirement in
rule 3 applies to `tag` only.
## Typical workflow
1. Understand what the user wants organized (a sender, a topic, a date
range, a specific account). To find *which* senders are clogging a
mailbox, use `mailctl senders "<query>" [--account NAME] [--top N]`
rather than guessing addresses and probing them one at a time with
`count`. When one sender needs splitting (receipts vs marketing),
use `mailctl subjects "<query>" --account NAME` to see which terms
actually cover the bucket instead of guessing subject keywords.
2. `mailctl search "<query>" [--account NAME]` to see what matches
and confirm the query is catching the right messages, and only
those.
3. `mailctl tag "<query>" --account NAME --add X --remove Y` (no
`--apply`) to preview the change.
4. Show the user the preview verbatim. Wait for confirmation.
5. Re-run with `--apply` only after confirmation.
## Command reference
```
mailctl search "<notmuch query>" [--account NAME] [--json]
mailctl show "<thread:id or id:msgid>"
mailctl tags
mailctl count "<query>" [--account NAME]
mailctl senders "<query>" [--account NAME] [--top N] [--json]
mailctl subjects "<query>" [--account NAME] [--top N] [--json]
mailctl tag "<query>" --account NAME [--add TAG]... [--remove TAG]...
[--apply] [--confirm-destructive] [--max-messages N]
mailctl draft --account NAME --to ADDR --subject TEXT
[--cc ADDR] [--body TEXT | --body-file PATH]
```
## Drafting mail
`mailctl draft` writes a real message into the account's local Drafts
maildir. It does not send anything, ever, there is no `--apply` step
because a draft has no side effect worth confirming beyond writing it.
Rules specific to drafting:
- `--account` is always required here too, same reasoning as `tag`:
the draft's `From` address is derived from the account, so an
ambiguous or wrong account means a draft that would send from the
wrong identity.
- After writing a draft, tell the user plainly: it's local until the
next mbsync run, it is not sent, and they need to open it in
neomutt (that account's Drafts folder, whatever it is named there,
or the postponed message list if configured) to review, edit, and
send it.
- If the user's request is vague about recipient or content ("draft
something to my accountant about the invoice"), ask for the missing
specifics rather than guessing a plausible-sounding email address or
inventing content they didn't ask for.
- Never draft and then take any further action on that draft, no
tagging it, no re-reading it back to "double check", the task ends
when the draft is written and reported.
Notmuch query syntax reference: `from:`, `to:`, `subject:`, `tag:`,
`date:`, boolean `and`/`or`/`not`. When in doubt about whether a query
is too broad, run `count` first before `tag`.
## What NOT to do, even if it seems helpful
- Don't infer that a broad cleanup request ("archive my old newsletters")
means every account, ask which account or offer to do them one at a
time.
- Don't chain multiple `--apply` tag operations without showing the
user each preview individually, batch confirmation of unreviewed
changes defeats the point of the dry-run step.
- Don't try to work around the no-sync rule by suggesting the user run
a sync "so I can help better" unless they ask whether fresher data
would help, that's information, not you initiating an action.
|