kuzymail 1.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. kuzymail-1.4.1/AGENT_USAGE.md +700 -0
  2. kuzymail-1.4.1/HUMAN_USAGE.md +310 -0
  3. kuzymail-1.4.1/LICENSE +661 -0
  4. kuzymail-1.4.1/MANIFEST.in +16 -0
  5. kuzymail-1.4.1/PKG-INFO +205 -0
  6. kuzymail-1.4.1/README.md +177 -0
  7. kuzymail-1.4.1/debian/.gitignore +7 -0
  8. kuzymail-1.4.1/debian/changelog +170 -0
  9. kuzymail-1.4.1/debian/control +29 -0
  10. kuzymail-1.4.1/debian/copyright +20 -0
  11. kuzymail-1.4.1/debian/kuzymail.install +3 -0
  12. kuzymail-1.4.1/debian/kuzymail.kuzymaild-worker@.service +23 -0
  13. kuzymail-1.4.1/debian/kuzymail.kuzymaild.service +19 -0
  14. kuzymail-1.4.1/debian/rules +16 -0
  15. kuzymail-1.4.1/debian/source/format +1 -0
  16. kuzymail-1.4.1/docs/CLIENT.md +508 -0
  17. kuzymail-1.4.1/docs/DAEMON_ARCHITECTURE.md +429 -0
  18. kuzymail-1.4.1/docs/DISTRIBUTION_PACKAGING.md +35 -0
  19. kuzymail-1.4.1/docs/reference.md +424 -0
  20. kuzymail-1.4.1/justfile +23 -0
  21. kuzymail-1.4.1/packaging/rpm/kuzymail.spec +61 -0
  22. kuzymail-1.4.1/pyproject.toml +64 -0
  23. kuzymail-1.4.1/setup.cfg +4 -0
  24. kuzymail-1.4.1/src/kuzymail/__init__.py +5 -0
  25. kuzymail-1.4.1/src/kuzymail/__main__.py +7 -0
  26. kuzymail-1.4.1/src/kuzymail/attention.py +299 -0
  27. kuzymail-1.4.1/src/kuzymail/attention_match.py +115 -0
  28. kuzymail-1.4.1/src/kuzymail/cli.py +6248 -0
  29. kuzymail-1.4.1/src/kuzymail/daemon.py +780 -0
  30. kuzymail-1.4.1/src/kuzymail/daemon_client.py +63 -0
  31. kuzymail-1.4.1/src/kuzymail/data/inject-adapters/default.json +67 -0
  32. kuzymail-1.4.1/src/kuzymail/data/prompt-templates/default.json +22 -0
  33. kuzymail-1.4.1/src/kuzymail/idle.py +167 -0
  34. kuzymail-1.4.1/src/kuzymail/inject_adapters.py +210 -0
  35. kuzymail-1.4.1/src/kuzymail/inject_state.py +197 -0
  36. kuzymail-1.4.1/src/kuzymail/injector.py +115 -0
  37. kuzymail-1.4.1/src/kuzymail/layout.py +150 -0
  38. kuzymail-1.4.1/src/kuzymail/mail_accounts.py +123 -0
  39. kuzymail-1.4.1/src/kuzymail/markdown.py +118 -0
  40. kuzymail-1.4.1/src/kuzymail/prompt_templates.py +532 -0
  41. kuzymail-1.4.1/src/kuzymail/schedule.py +157 -0
  42. kuzymail-1.4.1/src/kuzymail/settings.py +499 -0
  43. kuzymail-1.4.1/src/kuzymail/tokens.py +104 -0
  44. kuzymail-1.4.1/src/kuzymail/version.py +13 -0
  45. kuzymail-1.4.1/src/kuzymail.egg-info/PKG-INFO +205 -0
  46. kuzymail-1.4.1/src/kuzymail.egg-info/SOURCES.txt +55 -0
  47. kuzymail-1.4.1/src/kuzymail.egg-info/dependency_links.txt +1 -0
  48. kuzymail-1.4.1/src/kuzymail.egg-info/entry_points.txt +3 -0
  49. kuzymail-1.4.1/src/kuzymail.egg-info/requires.txt +4 -0
  50. kuzymail-1.4.1/src/kuzymail.egg-info/top_level.txt +1 -0
  51. kuzymail-1.4.1/tests/test_attention_cli.py +165 -0
  52. kuzymail-1.4.1/tests/test_attention_match.py +62 -0
  53. kuzymail-1.4.1/tests/test_daemon_logic.py +359 -0
  54. kuzymail-1.4.1/tests/test_inject_adapters.py +127 -0
  55. kuzymail-1.4.1/tests/test_layout_and_accounts.py +4580 -0
  56. kuzymail-1.4.1/tests/test_prompt_templates.py +182 -0
  57. kuzymail-1.4.1/tests/test_version.py +31 -0
@@ -0,0 +1,700 @@
1
+ # Agent Usage
2
+
3
+ This document is for agent participants using `kuzymail` as a client tool.
4
+ Profiles and accounts are configured locally on the machine where `kuzymail`
5
+ runs. A shared registry may provide optional metadata, but it is not required
6
+ for local setup.
7
+
8
+ It assumes a working mail service already exists.
9
+
10
+ Examples below assume `kuzymail` is installed or that the local `.venv` is
11
+ activated.
12
+
13
+ ## 1. Mental Model
14
+
15
+ Use:
16
+
17
+ - `kuzymail` as the canonical agent-facing facade for:
18
+ - account selection
19
+ - current-profile selection
20
+ - profile/runtime setup
21
+ - `sync`
22
+ - `list`
23
+ - `show`
24
+ - `attachments`
25
+ - `send`
26
+ - `reply`
27
+ - `archive`
28
+ - `delete`
29
+ - raw `notmuch` for deep search/debugging when needed
30
+
31
+ Output contract:
32
+
33
+ - `secret` stays plaintext because `mbsync` and `msmtp` consume it directly
34
+ - the agent-facing operational commands are JSON-first by default
35
+
36
+ You do not need to manage IMAP sessions or SMTP sessions manually.
37
+
38
+ Normal workflow:
39
+
40
+ 1. select your account once
41
+ 2. select your profile once
42
+ 3. sync your mailbox
43
+ 4. inspect/search local mail with `kuzymail list/show`
44
+ 5. send new mail with `kuzymail send` or answer with `kuzymail reply`
45
+ 6. archive or delete with explicit dry-run first
46
+
47
+ ## 2. Profiles
48
+
49
+ Create your local profile after Reception grants your service identity and you
50
+ have claimed your password:
51
+
52
+ ```bash
53
+ kuzymail profile create --profile zorbax --address zorbax@mail.example.invalid --display-name "Zorbax"
54
+ ```
55
+
56
+ Use `profile modify` for corrections to an existing profile:
57
+
58
+ ```bash
59
+ kuzymail profile modify --profile zorbax --display-name "Zorbax"
60
+ ```
61
+
62
+ Use this command to inspect the currently known mailbox inventory:
63
+
64
+ ```bash
65
+ kuzymail mailboxes
66
+ ```
67
+
68
+ Your profile determines:
69
+
70
+ - your email address
71
+ - where your local runtime is stored
72
+
73
+ ### Select your current profile
74
+
75
+ ```bash
76
+ kuzymail profile use --profile zorbax
77
+ ```
78
+
79
+ After that, most commands can omit `--profile`.
80
+
81
+ ## 2.1 Accounts
82
+
83
+ An account is one concrete mailbox account for a profile.
84
+
85
+ It carries:
86
+
87
+ - mailbox address
88
+ - login username
89
+ - secret key name
90
+ - IMAP host/port/security
91
+ - SMTP host/port/security
92
+
93
+ Account names are scoped by profile.
94
+ `zorbax/primary` and `quindle/primary` are different accounts even though the account id is the same.
95
+
96
+ Typical service setup:
97
+
98
+ ```bash
99
+ kuzymail account set \
100
+ --account primary \
101
+ --profile zorbax \
102
+ --address zorbax@mail.example.invalid \
103
+ --imap-host mail.example.invalid \
104
+ --imap-security ssl \
105
+ --smtp-host mail.example.invalid \
106
+ --smtp-security starttls \
107
+ --set-current
108
+ ```
109
+
110
+ Example second account for the same profile:
111
+
112
+ ```bash
113
+ kuzymail account set \
114
+ --account external \
115
+ --profile zorbax \
116
+ --address zorbax@example.net \
117
+ --login-user zorbax@example.net \
118
+ --secret-key ZORBAX_EXTERNAL_PASSWORD \
119
+ --imap-host imap.example.net \
120
+ --imap-security ssl \
121
+ --smtp-host smtp.example.net \
122
+ --smtp-security ssl
123
+ ```
124
+
125
+ Inspect configured accounts:
126
+
127
+ ```bash
128
+ kuzymail account list
129
+ kuzymail account show
130
+ ```
131
+
132
+ By default, account secrets are kept under:
133
+
134
+ ```text
135
+ secrets/profiles/<profile>/accounts/<account>.env
136
+ ```
137
+
138
+ Use `--secrets-file` only when you intentionally want an explicit shared or external env file.
139
+
140
+ ## 2.2 Profile Tokens
141
+
142
+ If a profile token is enabled, sensitive commands require one of:
143
+
144
+ - `--token-file <path>`
145
+ - `KUZYMAIL_TOKEN`
146
+
147
+ Example:
148
+
149
+ ```bash
150
+ kuzymail profile issue-token --profile zorbax
151
+ printf '%s' '...token...' > ~/.config/kuzymail/zorbax.token
152
+ kuzymail sync --token-file ~/.config/kuzymail/zorbax.token
153
+ ```
154
+
155
+ This is a local capability guard.
156
+ It is not strong host-level isolation.
157
+
158
+ ## 2.3 Strict Current Profile
159
+
160
+ In an agent-local environment, enable strict mode after selecting your profile:
161
+
162
+ ```bash
163
+ kuzymail profile use --profile zorbax
164
+ kuzymail profile strict --on
165
+ ```
166
+
167
+ After this, `kuzymail` refuses mailbox operations for another profile in the same environment.
168
+ This reduces accidental or casual cross-profile use; it does not replace host-level isolation.
169
+
170
+ ### Initialize a newly granted profile
171
+
172
+ After Reception grants mail access and you claim the one-time password, initialize the local runtime and secret like this:
173
+
174
+ ```bash
175
+ kuzymail account set --account primary --profile blorvax --address blorvax@mail.example.invalid --imap-host mail.example.invalid --imap-security ssl --smtp-host mail.example.invalid --smtp-security starttls --set-current
176
+ kuzymail init-profile --profile blorvax --password-prompt --set-current
177
+ ```
178
+
179
+ ## 3. Core Commands
180
+
181
+ ### Show runtime paths
182
+
183
+ ```bash
184
+ kuzymail paths
185
+ ```
186
+
187
+ Returns JSON.
188
+
189
+ ### Sync mailbox
190
+
191
+ ```bash
192
+ kuzymail sync
193
+ ```
194
+
195
+ Returns JSON, including captured `mbsync` / `notmuch` command output.
196
+
197
+ This pulls:
198
+
199
+ - `INBOX`
200
+ - `Sent`
201
+ - `Archive`
202
+
203
+ Then refreshes the local notmuch database.
204
+ It also runs `notmuch reindex '*'` every time. That is deliberate: on the current build, structured header queries become reliable only after reindex, so the wrapper pays the extra cost to keep search semantics correct.
205
+
206
+ ### Lightweight check
207
+
208
+ ```bash
209
+ kuzymail check
210
+ ```
211
+
212
+ Returns a single JSON object from an IMAP `STATUS` probe. It is read-only: no
213
+ message bodies or headers are fetched, no MIME is parsed, no local Maildir is
214
+ updated, and no IMAP flags are changed. Use `should_sync` and `sync_reason` to
215
+ decide whether to run a full `kuzymail sync`.
216
+
217
+ The check reads the local profile/account cursor from `imap-status.json` when it
218
+ exists. A successful `sync` refreshes that cursor with the latest `UIDNEXT` and
219
+ `UIDVALIDITY`; `check` never writes it. If no cursor exists yet, unseen mail can
220
+ still trigger `should_sync=true`, but UID advancement cannot be inferred.
221
+
222
+ ### Managed-mode inject prompt templates
223
+
224
+ `kuzymaild` wakes an agent with the `mail.digest` prompt template when managed
225
+ mail attention decides that new mail should be injected.
226
+
227
+ The upstream default is generic and policy-free:
228
+
229
+ ```text
230
+ Open one directly with `{show_command}`, or sync the mailbox with `{sync_command}`.
231
+ ```
232
+
233
+ Inspect and validate the effective template:
234
+
235
+ ```bash
236
+ kuzymail prompt template list
237
+ kuzymail prompt template show mail.digest
238
+ kuzymail prompt template render mail.digest
239
+ kuzymail prompt template validate
240
+ ```
241
+
242
+ Set an override from a text file:
243
+
244
+ ```bash
245
+ kuzymail prompt template set mail.digest --file template.txt
246
+ ```
247
+
248
+ Add local policy text without replacing the whole template:
249
+
250
+ ```bash
251
+ kuzymail prompt policy set --file policy.txt
252
+ ```
253
+
254
+ Precedence is package default, `/etc/kuzymail/prompt-templates.json`, user
255
+ override, profile override, then account override. Use `--profile` and
256
+ `--account` on the prompt commands to inspect or set profile/account scoped
257
+ overrides. Use `--system` only for host-wide policy.
258
+
259
+ Managed-mode inject adapters are also registry-backed. The package ships default
260
+ kuzywrap adapters under `kuzymail/data/inject-adapters/default.json`; host-wide
261
+ overrides can be placed in `/etc/kuzymail/inject-adapters.json`, and user
262
+ overrides in `$XDG_CONFIG_HOME/kuzymail/inject-adapters.json`. The attention
263
+ field `inject_wrapper` names one configured adapter. Adapter definitions use
264
+ argv lists, not shell strings. The packaged adapters are `kuzywrap-claude`,
265
+ `kuzywrap-codex`, `kuzywrap-codewhale`, and `kuzywrap-antigravity`;
266
+ Antigravity-hosted agents should use `kuzywrap-antigravity`, and
267
+ CodeWhale-hosted agents should use `kuzywrap-codewhale`.
268
+
269
+ ### List inbox threads
270
+
271
+ ```bash
272
+ kuzymail list
273
+ ```
274
+
275
+ ### Search / list with JSON
276
+
277
+ ```bash
278
+ kuzymail list --query "tag:inbox subject:BLOCKER"
279
+ ```
280
+
281
+ ### Show message or thread
282
+
283
+ ```bash
284
+ kuzymail show --message-id '<message-id>'
285
+ kuzymail show --thread 0000000000000001 --entire-thread
286
+ ```
287
+
288
+ ### Attachments
289
+
290
+ ```bash
291
+ kuzymail attachments list --message-id '<message-id>'
292
+ kuzymail attachments extract --message-id '<message-id>' --part 3 --output /tmp/file.bin
293
+ ```
294
+
295
+ ### Raw notmuch remains available
296
+
297
+ ```bash
298
+ notmuch --config runtime/profiles/zorbax/accounts/primary/notmuch-config search --sort=newest-first tag:inbox
299
+ notmuch --config runtime/profiles/zorbax/accounts/primary/notmuch-config search --sort=newest-first subject:extid-1
300
+ notmuch --config runtime/profiles/zorbax/accounts/primary/notmuch-config show --entire-thread=false id:<message-id>
301
+ ```
302
+
303
+ ### Threaded reply
304
+
305
+ ```bash
306
+ kuzymail reply \
307
+ --profile zorbax \
308
+ --message-id '<message-id>' \
309
+ --body "Reviewed. I agree with the proposed direction."
310
+ ```
311
+
312
+ `reply` is the safer default for agents: it replies only to `Reply-To` when
313
+ present, otherwise `From`, and removes the current account address. It does not
314
+ copy the original `To` or `Cc` recipients.
315
+
316
+ Use `reply --include-cc` when you intentionally want a sender-only reply that
317
+ also keeps the original `Cc` recipients. It still does not copy the original
318
+ `To` list.
319
+
320
+ Use `reply-all` only when you intentionally want to include the sender plus the
321
+ original thread recipients:
322
+
323
+ ```bash
324
+ kuzymail reply-all \
325
+ --profile zorbax \
326
+ --message-id '<message-id>' \
327
+ --x project thecost-accounting \
328
+ --x task-id extid-2026-03 \
329
+ --body "Reviewed. Including the thread."
330
+ ```
331
+
332
+ Use `reply-all --exclude-cc` when you want the sender plus original `To`
333
+ recipients, but do not want to copy the original `Cc` recipients.
334
+
335
+ Both reply commands report `to`, `cc`, and `external_recipients` in JSON.
336
+ `external_recipients` is visibility by default; account policy can require
337
+ `--allow-external` or block external delivery.
338
+
339
+ To add recipients deliberately, use additive flags:
340
+
341
+ ```bash
342
+ kuzymail reply \
343
+ --profile zorbax \
344
+ --message-id '<message-id>' \
345
+ --add-to reviewer@example.invalid \
346
+ --add-cc zorbax@example.invalid \
347
+ --add-bcc audit@example.net \
348
+ --x project thecost-accounting \
349
+ --x task-id extid-2026-03 \
350
+ --body "Reviewed. Explicit recipients included."
351
+ ```
352
+
353
+ `--add-to`, `--add-cc`, and `--add-bcc` are explicit additions to the computed
354
+ reply recipients. kuzymail removes the current account only from implicit
355
+ computed recipients; configured sender aliases are also treated as self
356
+ addresses for implicit reply/reply-all filtering. If an agent explicitly adds
357
+ its own address, it is kept.
358
+ Bcc recipients are included in the SMTP envelope and local JSON output, but are
359
+ not written into the outgoing message headers or the sent-copy serialization.
360
+ `send` also accepts repeatable `--bcc` for envelope-only Bcc recipients.
361
+
362
+ For multiline replies, prefer `--body-stdin` or `--body-file -`. Inline
363
+ `--body` text interprets shell-safe escaped newlines by default: `\n`, `\r`,
364
+ `\t`, and `\\`. Pass `--no-body-escape` only when you need literal backslashes.
365
+ `send`, `reply`, and `reply-all` reject empty bodies by default; pass
366
+ `--empty-body` only when an empty message body is intentional. `send` also
367
+ rejects an empty `--subject` unless `--empty-subject` is passed.
368
+
369
+ ### HTML mail
370
+
371
+ Plain text remains the default. To send HTML, provide both an explicit
372
+ plain-text body and an HTML body; kuzymail emits `multipart/alternative` with
373
+ the plain part first.
374
+
375
+ ```bash
376
+ kuzymail send \
377
+ --profile zorbax \
378
+ --to quindle@mail.example.invalid \
379
+ --subject "Readable report" \
380
+ --x project kuzymail \
381
+ --x task-id html-report-1 \
382
+ --body-file plain.txt \
383
+ --html-body-file report.html
384
+ ```
385
+
386
+ Markdown mail is explicit and symmetric with HTML input. Use `--markdown-body`,
387
+ `--markdown-body-file`, or `--markdown-stdin`; kuzymail renders the Markdown to
388
+ the HTML part and uses the original Markdown as the plain-text part unless an
389
+ explicit `--body`, `--body-file`, or `--body-stdin` fallback is provided.
390
+ Markdown options cannot be combined with `--html-body`, `--html-body-file`, or
391
+ `--html-stdin`. Markdown images with `http://` or `https://` sources remain
392
+ remote HTML images. Non-remote Markdown image targets are local files, resolved
393
+ relative to the Markdown file directory for `--markdown-body-file`; they must
394
+ exist as regular files and are attached inline with `cid:` references.
395
+
396
+ ```bash
397
+ kuzymail send \
398
+ --profile zorbax \
399
+ --to quindle@mail.example.invalid \
400
+ --subject "Readable report" \
401
+ --x project kuzymail \
402
+ --x task-id markdown-report-1 \
403
+ --markdown-body-file report.md
404
+ ```
405
+
406
+ ### Sending attachments
407
+
408
+ `send` and `reply` accept repeatable `--attach` / `--attachment` flags:
409
+
410
+ ```bash
411
+ kuzymail send \
412
+ --profile zorbax \
413
+ --to quindle@mail.example.invalid \
414
+ --subject "Review request" \
415
+ --x project kuzymail \
416
+ --x task-id contract-review-1 \
417
+ --body "Attached for review." \
418
+ --attach ./contract.tex
419
+ ```
420
+
421
+ ### Archive
422
+
423
+ Dry-run first:
424
+
425
+ ```bash
426
+ kuzymail archive --profile zorbax --thread 0000000000000001
427
+ ```
428
+
429
+ Apply:
430
+
431
+ ```bash
432
+ kuzymail archive --profile zorbax --thread 0000000000000001 --yes
433
+ ```
434
+
435
+ This also removes the affected messages from the local `notmuch` inbox/unread view so the operator-visible state stays coherent after the IMAP move.
436
+
437
+ ### Delete
438
+
439
+ Dry-run first:
440
+
441
+ ```bash
442
+ kuzymail delete --profile zorbax --message-id '<message-id>'
443
+ ```
444
+
445
+ Apply:
446
+
447
+ ```bash
448
+ kuzymail delete --profile zorbax --message-id '<message-id>' --yes
449
+ ```
450
+
451
+ This also tags the affected local messages as `deleted` and removes them from the `notmuch` inbox/unread view.
452
+
453
+ ## 4. Sending Mail
454
+
455
+ Example:
456
+
457
+ ```bash
458
+ kuzymail send \
459
+ --profile zorbax \
460
+ --to quindle@mail.example.invalid \
461
+ --subject "Proposal: namespace cleanup" \
462
+ --body "Intent: tighten noisy IDs. Action requested: review proposed cleanup."
463
+ ```
464
+
465
+ Use `--from` only for the account address or an explicitly configured sender
466
+ alias. This keeps the same SMTP login, kuzymail secret, runtime account, Sent
467
+ copy, and notmuch index while changing the RFC822 `From:` header:
468
+
469
+ ```bash
470
+ kuzymail account modify \
471
+ --profile ada \
472
+ --account proton-bridge \
473
+ --sender-alias "Ada Example <ada@example.org>"
474
+
475
+ kuzymail send \
476
+ --profile ada \
477
+ --account proton-bridge \
478
+ --from "Ada Example <ada@example.org>" \
479
+ --to customer@example.org \
480
+ --subject "Order update" \
481
+ --body "Your order has been updated."
482
+ ```
483
+
484
+ Unauthorized `--from` addresses fail before SMTP with `send_from_not_allowed`.
485
+ Header injection and multi-address values are rejected. The SMTP provider must
486
+ also know the alias; otherwise it may reject the message even after kuzymail
487
+ accepts the local configuration. Use `--dry-run` to inspect `from`,
488
+ `from_address`, `account_address`, and `login_user` before sending. `reply` and
489
+ `reply-all` accept the same `--from` option.
490
+
491
+ Outbound attributes are optional by default. Add them when they help audit,
492
+ routing, or operational context:
493
+
494
+ ```bash
495
+ kuzymail send \
496
+ --profile zorbax \
497
+ --to quindle@mail.example.invalid \
498
+ --subject "Proposal: namespace cleanup" \
499
+ --x project thecost-accounting \
500
+ --x task-id extid-2026-03 \
501
+ --body "Intent: tighten noisy IDs. Action requested: review proposed cleanup."
502
+ ```
503
+
504
+ `--x NAME VALUE` is a short alias for `--attribute NAME VALUE`. Attribute names
505
+ map directly to custom mail headers: `--x project email2` emits
506
+ `X-Project: email2`; `--x x-task-id abc` emits `X-Task-ID: abc`. Attribute names
507
+ are case-insensitive for validation and duplicate detection.
508
+
509
+ For multiline mail, prefer stdin:
510
+
511
+ ```bash
512
+ cat <<'EOF' | kuzymail send \
513
+ --profile zorbax \
514
+ --to quindle@mail.example.invalid \
515
+ --subject "Proposal: namespace cleanup" \
516
+ --x project thecost-accounting \
517
+ --x task-id extid-2026-03 \
518
+ --body-stdin
519
+ Intent: tighten noisy IDs.
520
+ Action requested: review proposed cleanup.
521
+ Why now: the current state is too noisy to trust.
522
+ EOF
523
+ ```
524
+
525
+ Multiple recipients:
526
+
527
+ ```bash
528
+ kuzymail send \
529
+ --profile zorbax \
530
+ --to quindle@mail.example.invalid \
531
+ --to vornix@mail.example.invalid \
532
+ --subject "Handoff: matcher analysis" \
533
+ --x project thecost-core \
534
+ --x task-id matcher-7 \
535
+ --body "Passing the current state and next recommended step."
536
+ ```
537
+
538
+ With CC:
539
+
540
+ ```bash
541
+ kuzymail send \
542
+ --profile zorbax \
543
+ --to quindle@mail.example.invalid \
544
+ --cc umbrik@mail.example.invalid \
545
+ --subject "Escalation: external ID policy" \
546
+ --x project thecost-accounting \
547
+ --x task-id extid-escalation-2 \
548
+ --escalation HUMAN_REQUIRED \
549
+ --body "Need a human decision on long-term policy."
550
+ ```
551
+
552
+ ## 5. Structured Header Contract
553
+
554
+ Attribute semantics:
555
+
556
+ - `--x project email2` -> `X-Project: email2`
557
+ - `--x task-id extid-1` -> `X-Task-ID: extid-1`
558
+ - `--x x-project email2` -> `X-Project: email2`
559
+ - `--x my_important_field yes` -> `X-My-Important-Field: yes`
560
+
561
+ Optional:
562
+
563
+ - `--escalation` -> `X-Escalation`
564
+ - `--header NAME VALUE` -> sets a standard outbound header from a fixed allow-list
565
+ (`Auto-Submitted`, `List-Unsubscribe`, `List-Unsubscribe-Post`). Repeat for
566
+ multiple headers. Kept separate from `--attribute`/`--x` so arbitrary custom
567
+ headers can't be smuggled in through this path. Names are case-insensitive
568
+ and emitted in canonical casing. Duplicate names (including case variants)
569
+ and values containing CR or LF are rejected.
570
+
571
+ If an operator wants `X-Agent-Type`, pass `--x agent-type VALUE` explicitly.
572
+
573
+ To make attributes mandatory or restrict their names for a specific account:
574
+
575
+ ```bash
576
+ kuzymail account modify \
577
+ --profile zorbax \
578
+ --account primary \
579
+ --attributes-allowed project \
580
+ --attributes-allowed task-id \
581
+ --attributes-allowed context \
582
+ --attributes-mandatory project
583
+ ```
584
+
585
+ To return to free-form optional attributes:
586
+
587
+ ```bash
588
+ kuzymail account modify --profile zorbax --account primary --attributes-allow-any --attributes-clear-mandatory
589
+ ```
590
+
591
+ Attributes are included in JSON output. They are not for secrets.
592
+ Standard outbound headers are reported separately as `standard_headers` in
593
+ `send` and dry-run JSON, using canonical names and their final values.
594
+
595
+ Use `--dry-run` on `send`, `reply`, and `reply-all` before risky outbound mail.
596
+ It builds the message plan and validates recipients, attributes, attachments,
597
+ and policy without SMTP or sent-copy side effects.
598
+
599
+ External recipients are visible in JSON by default. Accounts can tighten this:
600
+
601
+ ```bash
602
+ kuzymail account modify \
603
+ --profile zorbax \
604
+ --account primary \
605
+ --external-recipients-policy require-confirm
606
+ ```
607
+
608
+ With `require-confirm`, pass `--allow-external` for an intentional external
609
+ send. With `block`, external recipients are rejected even if
610
+ `--allow-external` is present.
611
+
612
+ Recommended escalation values:
613
+
614
+ - `NONE`
615
+ - `HUMAN_REQUIRED`
616
+
617
+ ## 6. Query Prefixes in Notmuch
618
+
619
+ Configured custom query prefixes:
620
+
621
+ - `Escalation:`
622
+
623
+ Examples:
624
+
625
+ ```bash
626
+ notmuch --config runtime/profiles/zorbax/accounts/primary/notmuch-config search --sort=newest-first Escalation:HUMAN_REQUIRED
627
+ ```
628
+
629
+ ## 7. What Send Actually Does
630
+
631
+ When you call `send` or `reply`, the facade:
632
+
633
+ 1. builds an RFC822 message
634
+ 2. sends it through `msmtp` to local authenticated SMTP submission
635
+ 3. archives the same message into local `Sent`
636
+ 4. syncs `Sent`
637
+ 5. refreshes the notmuch database
638
+
639
+ So your `Sent` view should remain coherent without manual steps.
640
+ It also pays the `notmuch reindex '*'` cost so local index state remains truthful
641
+ after generated sent-copy changes.
642
+
643
+ If `--from` uses a configured sender alias, the login and envelope config still
644
+ come from the account. kuzymail does not create a second profile/runtime just to
645
+ send as an alias.
646
+
647
+ Known addresses are formatted as `Display Name <email>` using local profiles first, then the optional shared registry.
648
+
649
+ `archive` and `delete` are intentionally dry-run by default and only mutate the real mailbox with `--yes`.
650
+
651
+ ## 8. Expectations and Boundaries
652
+
653
+ This is a message system and lightweight agent facade, not an execution channel.
654
+
655
+ Do not treat mail as:
656
+
657
+ - shell command transport
658
+ - arbitrary code transport
659
+ - secret exfiltration path
660
+
661
+ Use it for:
662
+
663
+ - handoffs
664
+ - dissent
665
+ - blockers
666
+ - context updates
667
+ - escalation
668
+
669
+ ## 9. Debugging
670
+
671
+ If sync fails:
672
+
673
+ ```bash
674
+ mbsync -c runtime/profiles/zorbax/accounts/primary/mbsyncrc zorbax-inbox zorbax-sent
675
+ ```
676
+
677
+ If the wrapper output feels surprising:
678
+
679
+ ```bash
680
+ kuzymail list --profile zorbax --query "tag:inbox" --limit 5
681
+ kuzymail show --profile zorbax --message-id '<message-id>'
682
+ ```
683
+
684
+ If search feels wrong:
685
+
686
+ ```bash
687
+ notmuch --config runtime/profiles/zorbax/accounts/primary/notmuch-config search '*'
688
+ ```
689
+
690
+ If send fails:
691
+
692
+ ```bash
693
+ msmtp -C runtime/profiles/zorbax/accounts/primary/msmtprc --serverinfo
694
+ ```
695
+
696
+ Use raw mail tools only when needed:
697
+
698
+ - `mbsync`
699
+ - `notmuch`
700
+ - `msmtp`