rmut-front 2.10.0

the part of an rmut front end that is not a toolkit: keys, keymaps, layout and formatting shared by the terminal and window front ends
Documentation
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
# rmut

A mutt replacement in Rust: terminal mail client with mutt keybindings,
built on ratatui. See [docs/PLAN.md](docs/PLAN.md) for the roadmap.

## Status

**2.0**: rmut is a daily-drivable mutt replacement: mutt's index,
pager and compose workflow under mutt's default keys, over maildir,
mbox and IMAP/SMTP (TLS, OAuth2), with PGP through gpg(1). 2.0 marks
the parity roadmap finishing, not a break: every default mutt key
does something, a muttrc imports (`--import-muttrc`) and loads
without errors, and every setting in the parity fixture
(`tests/e2e/muttrc-parity.rc`, the hundred-odd that turn up in every
dotfiles repo) is carried over or answered by what rmut does
instead. The config format did not change; a 1.x config is a 2.0
config.

What is there, briefly: an index with threads (References and
In-Reply-To, plus mutt's grouping by subject for mail that carries
neither, folding, thread operations), mutt's patterns for limit,
search, tag and delete, sort orders, `%`-format strings for the
index, status and pager lines, colours by pattern and by depth of
quoting, a sidebar and a folder browser with new-mail counts, a
compose menu with attachments (files, messages, renames, dispositions),
PGP sign/encrypt, postpone and recall, Fcc, identities and hooks
(folder, message, reply, fcc, crypt), aliases and query_command
completion, macros and mutt's enter-command (`:set`, `:bind`,
`:color`, the `un*` family, `reset`), IMAP with a local cache, IDLE,
transparent reconnect, server-side search and folder management, mbox
with write-back, notmuch search into a virtual mailbox, undo and
undo-send, and a man page (`docs/rmut.1`).

Not there, on purpose: S/MIME, POP3, scoring, MH/MMDF folders,
compressed-folder hooks, an embedded scripting language and an HTML
rendering engine. Still to come in 2.x: `$tunnel` and accept-once
for unknown certificates, and the PGP odds (opportunistic
encryption, inline PGP). The round-by-round history is in
[docs/PLAN.md](docs/PLAN.md).

A pty-driven e2e suite (including fake IMAP/SMTP servers and a
stub gpg) lives in `tests/e2e/`.

## Install & run

```sh
cargo install rmut                     # the `rmut` binary
# or from a checkout:
cargo install --path crates/rmut
# or during development:
cargo run -p rmut -- ~/Maildir         # or: just run ~/Maildir
```

Tagged releases on GitHub carry a prebuilt x86_64-linux binary with
the man page; `man docs/rmut.1` previews the manual from a checkout.

```
usage: rmut [MAILDIR | MBOX | imap:ACCOUNT[/FOLDER]]   (-V version, -h help)
       rmut --import-muttrc [-w] [MUTTRC]
```

Without an argument rmut looks where mutt looks: the first configured
mailbox, then `[mail] folder`, `$MAIL` (a maildir or an mbox file),
`~/Maildir`, `~/Mail`, `~/mail`, and finally the `/var/mail/$USER`
spool. A directory of maildirs answers with its `inbox`. When nothing
turns up it says where it looked, and how to make one:

```sh
mkdir -p ~/Mail/inbox/{cur,new,tmp} && rmut ~/Mail/inbox
```

The same mail in a window is `rmut-egui`, the egui front end over the
same session:

```sh
cargo install rmut-egui
just install-desktop                   # and a menu entry for it
```

`just install-desktop` writes
`~/.local/share/applications/rmut-egui.desktop` from
[crates/rmut-egui/dist](crates/rmut-egui/dist), pinning `Exec` to the
installed binary because a desktop session rarely carries
`~/.cargo/bin` on its `PATH`; a packager installs the file as it
stands. The entry claims no `mailto:` handler on purpose: the window
takes a mailbox, not a draft, and `mailto:` is the terminal binary's.

## The screen

Four regions, as in mutt: the help bar at the top, the mailbox or the
message, the status bar, and the message line under it. The message
line is where prompts, notes and errors go, so the status bar always
says which mailbox you are in and what is in it, whatever else is
happening.

## Keys

Index: `j`/`k` move, `Enter` view, `=`/`*` first/last, PgUp/PgDn or
Ctrl+B/Ctrl+F page, `d`/`u` delete/undelete, `F` flag, `N` toggle
read, `t` tag + `;` apply the next mark, save, copy, pipe, print or
bounce to all tagged, `z` undo
the last of those (or cancel a held send), `s` save
(copy to a mailbox + mark deleted), `$` sync changes to disk (asks
before purging deleted messages, like mutt: Enter takes the yes, and
`[mail] delete = "yes"` skips the question), Space page down,
Ctrl+L repaint, `!` shell command, Ctrl+Z suspend (fg brings it
back), `o` sort
(`d`ate `f`rom t`o` `s`ubject si`z`e `t`hreads `y`label `u`nsorted,
uppercase reverses),
Alt+v/Alt+V fold thread/all, Alt+d/Alt+u/Alt+t delete/undelete/tag a
whole thread, Ctrl+D/Ctrl+U the same for a subthread, Alt+n/Alt+p
type a number then Enter to jump to that message, `H`/`M` move to the
top/middle of the page, `@` show the
sender's full address, `%` toggle the mailbox read-only,
step between threads, Ctrl+R/Alt+r mark a thread/subthread read, `P`
jump to the parent, `#` break a thread, `&` link the tagged messages
under the cursor (all of these want thread sort), Alt+s/Alt+C
decode-save/decode-copy (the message as the pager shows it), `Y` edit the
X-Label, `V` show the version, Alt+l show the active limit, `l` limit,
`/` search
+ `n` next (Alt+/ searches backwards, and `n` then keeps going that
way; `n` wraps around the ends unless `[mail] wrap_search = false`),
`c` open mailbox by path (Alt+c opens it read-only; Tab completes
mailboxes,
account folders and nearby maildirs; empty Tab opens the folder
browser), `y` folder browser (with
new/unseen counts; folders holding new mail show bold), `G` check
for new mail now, `B` toggle the sidebar (Ctrl+N/Ctrl+P move its
highlight, Ctrl+O opens the highlighted mailbox), `v` attachments,
`m` compose, `r` reply, `g` group
reply, `L` list-reply, `f` forward, `C` copy to a mailbox (no delete mark), `|` pipe
the raw message to a shell command, `b` bounce (resend as-is to new
recipients, with a Resent-\* block), `e` edit the raw message (mutt's
edit; the changed result replaces the original), Alt+e edit as a new
draft (resend), `a` add the sender to the alias file (nick prompted, local
part prefilled), `p` print (pipes the message to `mail.print`,
default `lpr`), `~` mark-message (a stroke becomes a hotkey that
jumps back to the message), Esc+L list-action (the message's List-*
headers: help, post, subscribe, unsubscribe, archives, owner; mailto:
ones compose), and, unbound as in mutt but bindable by name,
`next-unread-mailbox`, `purge-message` (delete past the trash),
`error-history` and `what-key`, `:` run a config command (see **Enter-command**
below), `q` quit (writes changes; asks before purging
deletions, like mutt), `x` abort without saving. Leaving a mailbox
ages unread new mail to old (`O`), mutt's mark_old.

Pager: `j`/`k` scroll, `Space`/`-` page down/up, `J`/`K` next/previous
message, `d` delete and advance, `u`/`F`/`t` undelete/flag/tag without
leaving the message, `h` toggle full headers,
`v` attachments, `m`/`r`/`g`/`L`/`f` compose/reply/list-reply/forward,
`p` print,
`s` save, `C`/`|`/`b` copy/pipe/bounce, Alt+s/Alt+C decode-save/copy,
`e`/Alt+e edit raw/resend,
`:` run a config command, Ctrl+B the message's links, `q`/`i` back.
The URLs in the pager are clickable in a terminal that knows OSC 8
hyperlinks (kitty, foot, WezTerm, iTerm2, VTE terminals, Windows
Terminal), a wrapped one opening whole from either row; Ctrl+B lists
them, Enter opens one with `[ui] url_command`, `y` copies it through
OSC 52, which works over ssh with nothing installed. Space past the end opens the next message and wrapped
lines carry a leading `+` marker, like mutt. Replies ask mutt's
ask-yes questions: Reply-To (when the header is set), "No subject,
abort?", and "Include message in reply?"; Enter takes the yes.

Attachments: `Enter` view a text part, `s` save part to a file, `f`
forward the part (text quoted, anything else attached).

Patterns (limit/search): `~f x` from, `~s x` subject, `~b x` body,
`~t x` to, `~c x` cc, `~C x` to-or-cc, `~e x` sender, `~h x` any
header, `~i x` Message-ID, `~x x` References, `~d spec` date,
`~r spec` received date, `~m spec` index range, `~z spec` size range,
`~=` duplicate (same Message-ID twice), `~(P)`/`~<(P)`/`~>(P)` thread,
parent or child matches P, `~v` folded thread head, `~$` unreferenced,
`~y x` X-Label, `~L x` from-or-to, `~B x` whole message (headers and
body),
`~N` new, `~O` old, `~R` read, `~Q` replied, `~F` flagged, `~D` deleted,
`~U` unread, `~T` tagged,
`~l` addressed to a known mailing list, `~u` to a subscribed list,
`~p` addressed to me,
`~P` sent by me, `~A` every message; a bare (single) word with no `~`
expands through `[mail] simple_search` (mutt's, default `~f %s | ~s %s`,
so subject or from); set it to `~f %s | ~s %s | ~b %s` to search the
body too. `x` is a
case-insensitive regex (`"quotes"` keep spaces; an invalid regex falls
back to plain substring). `~d` takes a day or range
(`24/12/2026`, `1/6/2026-30/6/2026`, `24/12-`, `-1/1/2027`) or an
offset: `<1w` (within), `>2d` (older than), `=3d` (that day); units
y m w d H M, and `~r` takes the same specs against delivery time.
`~m` counts the index as shown (`~m 10-20`, `~m 5-`, `~m -20`), with
`.` for the selected message and `$` for the last (`~m .-$`). `~z`
takes `>100K`, `<2M`, `1K-2M`, or a plain byte count (K/M/G are
powers of 1024). Adjacent terms AND, `!` negates, `|` ORs, `()`
groups: `!~D (~f jane | ~t jane) ~d <1m`.

## IMAP

`rmut imap:work` (or `imap:work/Archive`) opens an account folder;
`c` and the folder browser `y` take the same specs, and `y` lists the
account's folders via LIST. In the browser, `C` creates a folder
(type an `imap:account/name` spec for a remote one), `d` deletes the
selected mailbox (confirmed), `r` renames it, and `s`/`u`
subscribe/unsubscribe: CREATE / DELETE / RENAME / SUBSCRIBE on the
open account, or the filesystem for a local maildir. Messages are mirrored into a cache maildir
under `~/.cache/rmut/imap/` (headers up front, full bodies fetched on
first view), so the index is fast and old mail reopens offline. `$`
pushes your changes to the server (flags via UID STORE, deletes via
EXPUNGE). New mail is announced by **IDLE** (RFC 2177, on a second
connection) and shows up within a second; when the server doesn't
support IDLE, the NOOP poll (`poll_seconds`) picks it up as before.
The folder browser asks the server for UNSEEN counts (STATUS). A
connection dropped by laptop sleep or a server timeout is transparently
reopened and the operation retried once; polls fetch only new arrivals
unless the server reported flag changes or expunges. The
password comes from `password_command` (e.g. `pass show mail/work`),
run once per session, or from a stored `password`, if you accept a
secret sitting in the config file (keep it chmod 600).

The connection runs on a thread of its own, so a slow server does not
stop the screen: the index keeps drawing, the keys keep working, the
message line says what is happening ("fetching the message... (Ctrl+G
aborts)"), and **Ctrl+G** gives up on it the way mutt's does, after
which the next operation reconnects. The poll for new mail, `$` sync,
fetching a message body, the sidebar's unread counts, opening another
folder or account, the folder browser's list, a server-side `~b`
search, saving to a server folder and the Fcc of a sent message all
work this way. While a folder opens, the keys typed meanwhile wait for
it, as they would in mutt, and Ctrl+G gives up on the open and leaves
you where you were; the browser opens at once and fills in when the
server's list arrives. Only the very first connect, before the screen
comes up, still waits, with its progress on the terminal.

A server that does not answer costs seconds, not the OS default of
about two minutes with nothing on screen: `[net] connect_timeout` (10s)
bounds the connection and `[net] timeout` (30s) bounds waiting for data
on a live one, and both say which host and what they were doing.
Both take `:set connect_timeout=...` / `:set net_timeout=...` at
runtime, and `--import-muttrc` brings mutt's `$connect_timeout` over.

TLS trusts the built-in Mozilla roots and, by default, the operating
system's certificate store on top (`[net] system_cas`, mutt's
`$ssl_usesystemcerts`); `[net] certificate_file` names a PEM of extra
roots for a private CA or a self-signed server. These only ever add
trust anchors, never replace the defaults. Imported from mutt's
`$certificate_file` / `$ssl_ca_certificates_file`; rmut has no
interactive accept-once, client certificates or `$tunnel` yet, so
those import as skipped.

For Gmail/O365-style **OAuth2**, set `auth = "xoauth2"` (or
`"oauthbearer"`, RFC 7628) and a `token_command` whose first output
line is a fresh access token; acquiring and refreshing tokens is the
external tool's business (oauth2ms, mutt_oauth2.py, ...). The command
runs for every connection, since tokens expire; both IMAP
(AUTHENTICATE) and SMTP (AUTH) then use the token instead of a
password.

## mbox

`rmut /var/mail/$USER` opens an mbox file: it is mirrored into a cache
maildir (like IMAP folders), so the index, pager, flags, and patterns
all work unchanged, and messages are keyed by content so your flags
survive when the spool grows. `$` sync writes changes back into the
file: deleted messages dropped, `Status:`/`X-Status:` headers
rewritten (`RO`/`AF`), mboxrd `>From` quoting preserved, all under an
exclusive flock, with a crash backup kept in the cache until the
rewrite lands, and refuses (rather than clobbers) when the spool
changed since the last look; check for new mail (`G`) and sync again.
New deliveries are picked up by the regular poll.

## PGP

PGP messages are handled on view by shelling out to gpg(1):
PGP/MIME (RFC 3156) and inline/clearsigned messages are decrypted
and/or verified, with a `[-- PGP: ... --]` verdict line at the top of
the pager (good/BAD/unverified signature). Outgoing mail is treated
per message: in the compose menu, `p` opens the security menu with
(e)ncrypt, (s)ign, (b)oth, (c)lear, and the chosen state shows in the
menu's Security line. Signing uses `sign_key` (or gpg's default key); encryption
looks keys up by recipient address and always encrypts to the sender
too, so the Fcc copy stays readable. A reply can inherit the
original's protection: `reply_sign` signs replies to signed mail,
`reply_encrypt` encrypts replies to encrypted mail, `reply_sign_encrypted`
signs replies to encrypted mail too (mutt's `$crypt_replysign` /
`$crypt_replyencrypt` / `$crypt_replysignencrypted`, all off by
default). Detection reads the original's MIME type only; it never
decrypts to decide. Passphrases are gpg-agent's
business; rmut never sees them.

## Sending mail

Drafts open in `$VISUAL`/`$EDITOR` (default `vi`). If an account with
`smtp_host` applies (the open mailbox's account, or the first one
configured), the message goes out via SMTP submission (STARTTLS on
587, implicit TLS on 465, AUTH PLAIN/LOGIN, Bcc stripped from the wire
copy) and the Fcc lands in the account's `sent_folder` by IMAP
APPEND. Otherwise it is handed to `sendmail -t -oi`; setting
`$RMUT_SENDMAIL` or `mail.sendmail` forces the sendmail path. `From:`
defaults to the identity in effect (`[identity]` overlaid by the open
account's `identity` and any matching `[[identities]]` rules, with
`reverse_name` picking the address a replied-to message came to), or
falls back to `$EMAIL` / `user@hostname`; the draft's own From line
always wins, and rmut prefills it whenever an identity applies. In
the compose menu, `P` postpones the draft into a nearby Drafts maildir
(or `.rmut-postponed`); the next `m` offers to recall it. Sent mail is
copied to a nearby Sent maildir when one exists (local mailboxes).
Aliases are read from `[mail] alias_file` (mutt's own setting),
`$RMUT_ALIASES`, or `~/.config/rmut/aliases`, one mutt-style
`alias nick address...` per line; `a` in the index appends to the same
file. At the To prompt (compose
and bounce), **Tab** completes the word under the cursor: alias nicks
by prefix, plus hits from `query_command` when one is configured
(mutt's protocol: `%s` is the search word, the first output line is a
message, then `address<TAB>name` lines). Repeated Tab cycles through
multiple matches.

A reply quotes the original under mutt's `$attribution` line, one
`$indent_string` per line, and a forward takes its subject from
`$forward_format`. All three are format strings over the message being
answered (`%a` address, `%n` name, `%f` the whole From header, `%s`
subject, `%i` message-id, `%d` date, `%{...}` strftime), and the
defaults are mutt's:

```toml
[mail]
attribution = "On %d, %n wrote:"   # the quoted reply's opening line
indent_string = "> "               # what each quoted line starts with
reply_regexp = "^(re)(\\[[0-9]+\\])*:[ \\t]*"  # what a reply subject
                                   # may already start with
forward_format = "[%a: %s]"        # the subject a forward carries
include = "ask-yes"                # quote the original: yes/no/ask-*
ask_cc = false                     # mutt's $askcc, between To and
ask_bcc = false                    # Subject; $askbcc follows it
forward_quote = false              # true indents the forwarded text
```

Every draft rmut starts can end with a signature (mutt's `$signature`
and `$sig_dashes`), and the two questions mutt asks on the way in are
settable (`$abort_nosubject`, `$abort_unmodified`):

```toml
[mail]
signature = "~/.signature"    # a file, or a command when it ends in |
sig_dashes = true             # the "-- " line above it (the default)
sig_on_top = false            # true puts the signature above the quote
hostname = "mail.example.net" # the Message-ID host (mutt's $hostname)
user_agent = false            # true adds a User-Agent: rmut/... header
abort_nosubject = "ask-yes"   # empty subject: yes/no/ask-yes/ask-no
abort_unmodified = true       # a first edit that changed nothing is
                              # not a message: the draft is dropped
```

The signature is read afresh for every draft, so `signature =
"fortune |"` says something new each time; one that cannot be read is
simply left off. A recalled postponed message keeps the signature it
was postponed with rather than gaining a second one.

By default (like mutt) the editor gets only the message body;
headers come from the prompts, and attachments are added with `a` at
the compose menu. The menu has mutt's heavier functions too: `A`
attaches the tagged messages of the open mailbox (the one under the
cursor when none is tagged) as message/rfc822, `n` makes a new file
of a given Content-Type and opens it in the editor, Ctrl+O renames a
file for sending, `u` marks it to be unlinked after the send, Ctrl+D
toggles inline/attachment, `K`/`J` move it up and down, `w` writes
the message as it stands to a mailbox without sending, `i` runs
`$ispell` over the draft, `V` views a file through its mailcap entry
and Esc+v as text. With `edit_headers = true` the draft's header block
is part of the editor buffer, where you can adjust To/Cc/Subject
directly and attach files with `Attach:` pseudo-headers:

```
To: jane@example.com
Subject: the report
Attach: ~/report.pdf the Q2 numbers
Attach: "/tmp/two words.png"
Attach: /tmp/build.log text/plain @name="log.txt" @inline @unlink notes

see attached
```

Each one becomes a base64 part of a multipart/mixed message (content
type guessed from the extension, the rest of the line an optional
description). After the editor you land in mutt's **compose menu**:
the draft's From/To/Cc/Bcc/Subject/Fcc/Security above the attachment
table (body, forwarded original, every `Attach:` file with size and
type). `y` sends, `e` reopens the editor, Enter views the selected
entry (text directly, other types via `[filters]`), `t`/`c`/`b`/`s`
edit the headers, `F`/`r` edit the From and Reply-To,
`a` attaches without a trip through the editor, `D` detaches
the selected file, `p` opens the security menu, `P` postpones, and
`q` asks "Postpone this message?" (no discards). `$postpone` sets that
question (yes/no skip it, ask-yes/ask-no pick the default). `M`
turns markdown compose on or off for the draft: it goes out as
multipart/alternative, the text as you typed it plus an html part
rendered from it as markdown, so a graphical reader sees the
formatting and a plain one loses nothing (`[mail] markdown = true`
makes it the default; the choice survives a postpone). PGP signing and encryption wrap the whole multipart, attachments
included; this also works for forwards with `forward = "attach"`.

With several postponed drafts, recalling (`m`, then `r`) opens a
picker instead of silently taking the newest. `$recall` controls the
offer: `no` never asks (always a new message), `yes` recalls the
newest outright, the default asks.

## Enter-command

`:` opens mutt's command prompt (the same line editor as the other
prompts, with its own history) and applies one config line to the
running session. Nothing is written back to the config file, so it is
a place to try a setting before keeping it.

```
:set index_format="%4C %Z %{%b %d} %-15.15L (%?l?%4l&%4c?) %s"
:set nobeep                 # also: set beep, unset beep, toggle beep
:reset pager_context        # back to the default (unset goes to nothing)
:set invtilde               # mutt's inv prefix toggles
:set pager_index_lines=6 pager_context=2
:set sort?                  # report a value instead of setting it
:bind index \Cd delete-message      # mutt keys and function names
:macro pager S "s=archive<enter>"
:color index brightyellow default ~F
:uncolor index ~F           # and uncolor index *, uncolor status, ...
:mono index bold ~N         # an attribute instead of a colour pair;
                            # unmono takes it back
:unhook folder-hook         # drop every hook of a type, or unhook *
:unmailboxes ~/Mail/old     # stop watching one, or unmailboxes *
:unalias bob                # out of the alias file, or unalias *
:ignore x-spam-score        # and unignore, to bring one back
:alternates 'jane@old\.example\.com'   # and unalternates (* clears)
:my_hdr Organization: Acme  # and unmy_hdr Organization (* clears)
:alias jane Jane Doe <jane@example.com>
:push "<enter>"             # keys into the input queue
:exec sync                  # run one function now
```

Settable at runtime: `index_format`, `date_format`, `sort`,
`sort_aux`, `pager_format`, `pager_index_lines`, `pager_context`,
`quote_regexp`, `wrap`, `tilde`, `status_format`, `theme`, `beep`,
`from`, `realname`, `reverse_name`, `edit_headers`, `fast_reply`,
`autoedit`, `copy`, `forward`/`mime_forward`, `sendmail`, `editor`,
`print_command`, `query_command`, `trash`, `record`, `postponed`,
`new_mail_command`, `mail_check`, `undo_send`, `metoo`, `text_flowed`,
`attribution`, `indent_string`, `forward_format`, `include`, `askcc`,
`askbcc`, `connect_timeout`, `net_timeout`, `pager_stop`, `markers`,
`smart_wrap`, `collapse_unread`, `uncollapse_jump`, `quit`,
`confirmappend`, `save_name`, `force_name`, `forward_quote`,
`signature`, `sig_dashes`, `abort_nosubject`, `abort_unmodified`,
`mark_old`, `print`, `beep_new`, `wait_key`, `reverse_realname`,
`delete_untag`, `flag_safe`, `maildir_trash`, `mail_check_recent`,
`check_new`, `uncollapse_new`, `menu_scroll`, `menu_context`,
`menu_move_off`, `help`, `error_history`, `sort_browser`,
`sort_alias`, `shell`, `tmpdir`, `ispell`,
`reflow_text`, `notmuch`, `sidebar_visible`,
`sidebar_width`, `pgp_sign_as`, `crypt_autosign`, `crypt_autoencrypt`,
`use_envelope_from` (and its old name `envelope_from`),
`envelope_from_address`, `dsn_notify`, `dsn_return`, `reply_self`,
`fcc_attach`, `fcc_clear`, `forward_edit`, `mime_forward_rest`,
`hyperlinks`, `url_command`, `markdown`.
An unknown option, a bad number, an unbindable key, or an unknown
function reports on the bottom line in the error color and stops the
rest of the line. Changed settings recompile in place: colors, key
tables, the quote regexp, header rules, and a resort when the sort
order moved.

## Command line

```
rmut [-R] [-e CMD]... [-p|-y] [-z|-Z] [-f MAILBOX | MAILBOX | mailto:URL]
rmut -s SUBJECT [-c CC] [-b BCC] [-a FILE]... [-i FILE] -- ADDRESS...
rmut --import-muttrc [-w] [MUTTRC]
```

`-R` read-only, `-f` the mailbox to open, `-p` the postponed picker,
`-y` the mailbox list, `-z`/`-Z` exit 1 instead of starting when the
mailbox is empty or has no new mail (for a prompt or a cron job), `-e`
runs an enter-command line before the first draw (repeatable).

A `mailto:` URL opens a prefilled draft (to, cc, bcc, subject, body,
percent-decoded) with the mailbox still open behind it, which is what
a desktop mail handler passes:

```
rmut 'mailto:jane@example.com?subject=Lunch&body=Friday%3F'
```

**Sending without the TUI**, for scripts and one-shot mail: `-s`,
`-c`, `-b`, `-a` (repeatable), or `-i`, or a bare `--`, puts rmut in
send mode. The body is stdin (or `-i FILE`), the recipients are the
remaining arguments, the identity and transport come from the config,
and the exit code says whether the message went out:

```
rmut -s "nightly build" -a build.log -- ops@example.com < report.txt
```

The sent copy goes to a local `mail.sent` maildir; a remote Sent
folder is left to the interactive send, since an IMAP APPEND needs the
account opened. A configured `signature` ends a batch message too, as
in mutt.

## Mailing lists

Tell rmut which addresses are lists and it stops guessing:

```toml
[mail]
lists      = ["announce@lists.example.com"]   # lists you read
subscribed = ["rmut-dev@lists.example.com"]   # lists you are on
```

Entries are case-insensitive regexes matched against addresses, and
`subscribed` counts as a known list too. mutt's `lists`, `subscribe`,
`unlists` and `unsubscribe` are imported.

With that, `L` replies to the list alone: the List-Post address when
the list published one, otherwise the known list address from To/Cc.
On a message from no known list it refuses rather than quietly mailing
the author. `~l` limits to list mail, and `%L` in the index format
already shows "To <list>".

Mail going to a known list carries a `Mail-Followup-To` so replies
land on the list (mutt's $followup_to). On a list you are subscribed
to, your own address is left out, since the list copy is the one you
will get; on a list you only read, it stays in. A sender's own
`Mail-Followup-To` is honored by a group reply: it replaces the
recipient set rather than adding to it.

## Who counts as me, and my_hdr

`[identity] email`, the accounts, and the `[[identities]]` rules
already name your addresses. `alternates` adds the rest: an old
domain, a role address, whatever forwards to you.

```toml
[mail]
alternates = ['jane@old\.example\.com', '^(jane|jd)@example\.com$']
my_hdr     = ["Organization: Acme", "Bcc: jane@example.com"]
metoo      = false   # true: a group reply copies you too
```

Entries are case-insensitive regexes over the bare address, mutt's
`alternates`, and one answer serves everywhere rmut asks whether an
address is yours: `~p` and `~P`, the third `%Z` character (`+` sole
recipient, `T` one of several, `C` on the Cc, `F` sent by you, `L` to
a subscribed list), `reverse_name` picking the address a message came
to, and the dedup a group reply does. That dedup is mutt's: replying
to all drops your own addresses and the person already in the To, so
you get no copy of your own mail and nobody gets two. `metoo = true`
keeps you on the list, like mutt's $metoo.

`my_hdr` lines ride on every draft the compose menu, a `mailto:` URL,
or a batch send produces; with `edit_headers` they show in the editor
like any other header. One naming a header rmut already wrote replaces
it, so `my_hdr From:` and `my_hdr Reply-To:` win; `To`, `Cc` and `Bcc`
gain the address instead, so a standing `my_hdr Bcc:` cannot erase a
reply's recipients. There is one entry per header name: a later
`my_hdr` for the same header replaces the earlier one, and `unmy_hdr`
takes it off. mutt's `alternates`, `unalternates`, `my_hdr`,
`unmy_hdr` and `set metoo` are imported, and all of them work at the
`:` prompt.

## format=flowed

A `text/plain; format=flowed` part (RFC 3676) arrives split at
whatever width the sender's terminal happened to be. rmut puts it back
into paragraphs and lets the pager wrap it at your width, so `[pager]
wrap` and the window govern as they do for everything else. Quote
depth bounds a paragraph and survives as `>` marks, so quoted text
still colours and folds; space-stuffing is undone, `DelSp=yes` is
honoured, and `-- ` stays a fixed line. mutt's `$reflow_text` turns it
off:

```toml
[pager]
reflow_text = false   # keep the sender's line breaks

[mail]
text_flowed = true    # send text/plain; format=flowed
```

`text_flowed` declares the outgoing text part `format=flowed` and
space-stuffs it, in the plain case, under attachments, and inside a
PGP signature or encryption alike. The paragraphs themselves are your
editor's doing, exactly as in mutt: a line that continues has to end
with a space, and rmut adds none of its own.

## Mailbox names: `=` and `+`

Set `[mail] folder` and a mailbox can be named under it, the way mutt
does it:

```toml
[mail]
folder = "~/Mail"            # or an account: "imap:work"
mailboxes = ["=inbox", "=lists"]
trash = "=trash"
```

`=x` and `+x` mean `$folder/x`, and `=` alone is the folder itself.
It works wherever a mailbox is named: the change-folder prompt, save
and copy, the compose menu's Fcc, the configured mailboxes, trash,
sent, postponed and the fcc-hook targets. It also works in a macro,
which is what lets an imported mutt line like
`macro index S "<save-message>=archive<enter>"` land where it means
to. Tab completion still works on real paths only, so complete first
or type the shorthand whole.

With `folder = "imap:work"`, `=Archive` is that account's Archive
folder.

## Credentials

An account's password can sit in the config as `password = "..."`,
and `--import-muttrc` puts `set imap_pass` there because that is
where mutt had it. It is the weakest of the options: the config is a
plain file in `~/.config`, and anyone who can read it can read your
mail. rmut writes the files it creates at mode 600, and says so at
startup if a config holding a password is readable by anyone else:

```
chmod 600 /home/you/.config/rmut/config.toml (it holds a password and others can read it)
```

Better is to keep the secret somewhere else and name the command that
fetches it. `password_command` runs once per session, and its first
line of output is the password:

```toml
[[accounts]]
name = "work"
user = "jane"
# pick one:
password_command = "pass show mail/work"          # pass(1), GPG-backed
password_command = "gpg -q -d ~/.config/rmut/imap.gpg"   # a file you encrypted
password_command = "secret-tool lookup service imap user jane"   # libsecret
password_command = "cat /media/crypt/mail-pass"   # an encrypted volume
```

Anything that prints the password works. A GPG-backed one prompts
once per session and gpg-agent remembers it for a while; a command
that fails takes the connection down with its message, so a locked
store fails closed rather than silently.

For a provider that wants OAuth2 rather than a password, `auth =
"xoauth2"` (or `"oauthbearer"`) with `token_command` runs the token
helper for every connection, since tokens expire.

## Threads

`o t` sorts by threads, and then the thread is a unit you can act on:
Alt+d, Alt+u and Alt+t (mutt's `Esc d`, `Esc u`, `Esc t`) delete,
undelete and tag the whole thread the cursor sits in, Ctrl+D and
Ctrl+U do the same for the subthread (the message under the cursor
and its replies), and Alt+n / Alt+p step to the next and previous
thread. Alt+v folds one thread, Alt+V all of them.

Each of these is one undo step, however many messages hang off it, so
`z` brings a thread back whole. Deleting advances to the next
undeleted message, mutt's `$resolve`, which makes clearing thread
after thread one repeated key; undeleting and tagging stay put.
Tagging follows the cursor, so a second Alt+t untags the thread.
Without thread sort they refuse, as they do in mutt.

Ctrl+R and Alt+r (mutt's `Ctrl+R`, `Esc r`) mark the thread or the
subthread read, `P` jumps to the parent message (root-message is
there too, unbound), and tag-subthread is an action for `:bind`.

Some mail arrives with no In-Reply-To and no References at all: a
notification robot that mints a fresh Message-ID every time, a list
that strips the headers. Nothing but the subject holds those
together, so, like mutt, rmut hangs a thread root whose subject
repeats one already in the mailbox under the message that named it.
The oldest becomes the root, the rest a fan under it, and the tree
ends in a star instead of an arrow where the subject decided the
place. `[index] strict_threads = true` (mutt's `$strict_threads`)
turns it off; `sort_re = false` (mutt's `$sort_re`) widens it from
"Re:" subjects to any equal subject, which groups unrelated mail
sharing a subject like "hi".

Two keys edit the threading itself, since misconfigured mailers
leave replies dangling or bolt a new discussion onto an old one.
`#` (break-thread) takes the In-Reply-To and References off the
message under the cursor, so it and its replies become a thread of
their own; on a message the subject grouping placed, where there are
no headers to take off, it writes an `X-Rmut-Thread: broken` header
that the grouping honours from then on. That last part is rmut's
own: mutt has nowhere to record the break, so its subject pass hangs
the message straight back and the manual's answer is
`$strict_threads`. `&` (link-threads) makes the tagged messages replies to
the one under the cursor, as mutt does, by giving each an In-Reply-To
naming it (and untagging it). Both rewrite the message file in place
and are one undo step each: `z` writes the old headers back. They
work on local maildirs; on IMAP and mbox the real message lives
elsewhere, so they refuse rather than edit a copy.

The patterns know threads too: `~(P)` matches every message in a
thread where some message matches P (`~(~P)`: threads I took part
in), `~<(P)` the messages whose parent matches P (`~<(~P)`: replies
to my mail), `~>(P)` those with a child matching P, `~v` the head of
a folded thread, and `~$` a message with no parent and no children.
Outside thread sort `~(P)` reads as P and the rest are false.

A reply's subject is "Re: " over the original with whatever
`[mail] reply_regexp` matched at its start taken off (mutt's
`$reply_regexp`, default `^(re)(\[[0-9]+\])*:[ \t]*`), so "RE: x" and
"Re[2]: x" both answer as "Re: x"; a locale's prefixes go in as
`^(re|aw|sv):[ \t]*`. It is case-insensitive unless it holds an
uppercase letter, as mutt compiles it.

## Tagged operations

`t` tags a message and `;` hands the tagged set to the next function
(`Tag-` sits on the message line while it waits, as in mutt):
`d`/`u`/`F`/`N`/`t` mark them all, `s`/`C` save or copy them all (one
prompt, one undo step), `|` and `p` pipe or print them concatenated
into a single run of the command (mutt's `$pipe_split` unset, the
separator mutt's `$pipe_sep`; `pipe_split`/`print_split` = true run it
once per message instead), and
`b` bounces them all to the same addresses, with the confirmation
counting what it is about to do.

A function that cannot take a set says so ("resend does not take the
tagged set") rather than quietly acting on the one under the
cursor. `T` and Ctrl+T tag and untag by pattern; `;t` clears the tags,
the way it does in mutt.

## The attachment reminder

Set `[mail] abort_noattach` and a draft whose body mentions an
attachment when none is attached gets a question before it goes:

```toml
[mail]
abort_noattach = "ask"       # "no" (default), "ask", or "yes" (refuse)
attach_keyword = '\b(attach|attached|attachment)\b'   # the default
```

Answering `n` puts you back in the compose menu, where `a` attaches
the file you meant. Quoted lines and anything below a `-- ` signature
do not count, so a reply to someone else's "see attached" and a
signature advertising an attachment opener are not false alarms.

neomutt's `abort_noattach` and `abort_noattach_regex` import
(`ask-yes` and `ask-no` both become `ask`), and mutt's `\<` / `\>`
word edges are translated to `\b` on the way in. Batch sends do not
ask: there is no terminal to answer at.

## Undo

`z` walks back the last change to your messages: a delete or
undelete, a flag or read toggle, a tag, a `D`/`U`/`T`/Ctrl+T pattern
sweep, or a save or copy to another mailbox. One keystroke is one
step however many messages it touched, so a pattern delete over three
hundred messages comes back in one go, and a save's copy in the
target mailbox is removed again along with the original's delete
mark.

A step remembers the messages as they stood before it, and puts that
state back. Writing the mailbox ends what can be undone: `$` (and the
write on quit) drops the stack, because those changes are on disk and
the deleted ones are gone. Up to 32 steps are kept.

This one is rmut's own; mutt has nothing like it. It is cheap here
because rmut already defers every mark to the sync.

### Undo send

`[mail] undo_send` holds a sent message for that many seconds before
anything leaves the machine:

```toml
[mail]
undo_send = 10   # 0 (the default) sends at once, as mutt does
```

The status line counts the seconds down, and `z` takes the message
back: not just cancelled, but returned to its compose menu with the
draft as you left it, ready to edit and send again. A held message is
the most recent thing you did, so `z` reaches it before it reaches
the mark history.

The timer running out sends it, and so does leaving rmut: quitting is
not cancelling. Batch sends (`-s` and friends) never hold, since
there is no terminal to press `z` at.

### Rescuing one message from a run of deletions

Deleted messages stay in the index (they only go on `$`), so `j`/`k`
land on them and `u` puts one back. When there are many, three things
help: `U <pattern>` undeletes a whole set (`U ~f boss`), `l ~D` limits
the view to the marked ones, and a pattern search hops between them:
`/~D` then `n`, with Alt+/ to go the other way. The two macros above
put that hop on `.` and `,`.

In the pager, `j`/`k` step over deleted messages by design (mutt does
the same); `J`/`K` step to any message, deleted ones included, and
`u` puts one back without going out to the index.

## Which part shows, and mailcap

A `multipart/alternative` message carries the same text twice or more.
mutt's `alternative_order` decides which copy you read, most wanted
type first; `text/*` matches a whole main type, and anything not
listed falls back to rmut's ranking (a part with a filter, then
enriched over plain over html):

```toml
[pager]
alternative_order = ["text/plain", "text/html"]
```

`[filters]` is mutt's `auto_view`: a MIME type and the command that
turns it into text on stdout. Leave the command empty and rmut takes
it from your mailcap, exactly where mutt takes it from: the first
`copiousoutput` entry for the type, skipping one whose `test=` fails
or that wants the terminal, with `%s` given a temporary file. The
files are `$MAILCAPS`, or `~/.mailcap`, `/etc/mailcap`,
`/usr/etc/mailcap`, `/usr/local/etc/mailcap`. A type with no such
entry simply does not autoview, and the part stays an attachment.

`auto_view`, `unauto_view`, `alternative_order` and
`unalternative_order` all work at the `:` prompt as well, and import
from a muttrc.

## Hooks

Beyond `[[identities]]` (the from/realname half of folder-hook and
send-hook), four mutt hooks have tables of their own. The three that
carry a command line take exactly what the `:` prompt takes.

```toml
[[folder_hooks]]              # on opening a matching mailbox
folder  = "*work*"            # glob on the mailbox, path or imap: spec
command = 'set index_format="%4C %Z %-6d %-20.20F %s"'

[[message_hooks]]             # while that message is selected
pattern = "~f boss@example.com"
command = "set pager_context=5"

[[reply_hooks]]               # while a reply to it is built
pattern = "~f boss@example.com"
command = "set from=jane@work.example.com"

[[fcc_hooks]]                 # where the sent copy goes
pattern = '~t @work\.example\.com'
mailbox = "~/Maildir/.WorkSent"

[[crypt_hooks]]               # encrypt to this key for this recipient
address = "boss@example.com"
key     = "0xDEADBEEF"
```

A **message-hook** is in force only while its message is the selected
one: the moment the match set changes, every setting it touched goes
back to what it was, so a display setting really is per-message. A
**reply-hook** applies while the reply's draft is built, which covers
`set from`, `edit_headers` and `my_hdr`. A **folder-hook** is not
undone when you leave, exactly like mutt, so a catch-all entry
(`folder = "*"`) is how you put a setting back.

**fcc-hook** patterns match the draft as it stands after the editor,
so the compose menu's Fcc line already shows where the copy is going;
an Fcc chosen by hand with `f` still wins, and batch sends honour the
hook too. Bcc addresses join the Cc ones for matching, so `~c` sees a
blind recipient. **crypt-hook** replaces a recipient's address with a
key id when gpg is asked to encrypt.

## Configuration

`$RMUT_CONFIG` or `~/.config/rmut/config.toml`:

```toml
[identity]
name = "Jane Doe"            # From: Jane Doe <jane@example.com>
email = "jane@example.com"
reverse_name = false         # true: a reply's From becomes whichever
                             # of your addresses the mail was sent to
reverse_realname = true      # false: only the address comes over, the
                             # name above stays (mutt's setting)

[[identities]]               # conditional identity (folder-/send-hook):
folder = "*work*"            # glob on the open mailbox, and/or
recipient = "*@work.example.com"   # glob on a draft recipient;
name = "Jane Work"           # matching rules overlay [identity] in
email = "jane@work.example.com"    # order, unset fields fall through

[mail]
folder = "~/Maildir"         # mutt's $folder: "=x" and "+x" name a
                             # mailbox under it, at a prompt or in a
                             # macro ("imap:work" works too)
mailboxes = ["~/Maildir"]    # default mailbox + folder browser entries;
                             # local ones are watched for new mail
                             # ("new mail in ..." in the status line)
sent = "~/Maildir/.Sent"     # Fcc target (else a nearby Sent is used)
postponed = "~/Maildir/.Drafts"
sendmail = "/usr/sbin/sendmail"
editor = "vim"
poll_seconds = 5             # new-mail check interval
print = "lpr"                # `p` pipes the message here
save = "~/Maildir/.Archive"  # default target for `s`
forward = "inline"           # or "attach" (original as message/rfc822)
query_command = "khard email --parsable %s"   # Tab completion lookup
trash = "~/Maildir/.Trash"   # purged mail moves here (mutt's $trash;
                             # imap:acct/Trash for IMAP mailboxes);
                             # purging inside it deletes for real
edit_headers = false         # true: the header block is part of the
                             # editor buffer (To/Cc/Subject, Attach:)
alternates = ['jane@old\.example\.com']   # my other addresses
my_hdr = ["Organization: Acme"]           # on every draft
metoo = false                # true: a group reply copies me too
text_flowed = false          # true: send text/plain; format=flowed
markdown = false             # true: text/plain as typed plus text/html
                             # rendered from it (M in the compose menu
                             # decides per draft)
undo_send = 0                # seconds a sent message waits, so z can
                             # take it back (0 sends at once)
delete = "ask"               # mutt's $delete: "yes" purges without
                             # asking, "no" keeps the marks
abort_noattach = "no"        # "ask"/"yes": a body that mentions an
                             # attachment with none attached is
                             # questioned before it goes
use_envelope_from = false    # true: sendmail -f (envelope_from_address,
                             # else the From); SMTP's MAIL FROM too
dsn_notify = "failure,delay" # delivery status notifications: sendmail
dsn_return = "hdrs"          # -N/-R, NOTIFY=/RET= when SMTP offers DSN
reply_self = false           # true: a reply to my own mail comes back
                             # to me, not to its recipients
fcc_attach = "yes"           # "no": the sent copy keeps the text only;
                             # ask-yes/ask-no ask at send time
fcc_clear = false            # true: keep the copy of signed/encrypted
                             # mail in the clear
forward_edit = "yes"         # "no": a forward skips the editor;
                             # ask-yes/ask-no ask first
mime_forward_rest = true     # f in the attachment menu attaches a
                             # part that is not text; false refuses
signature = "~/.signature"   # ends every draft; a name ending in |
                             # is a command whose output it is
sig_dashes = true            # the "-- " line above the signature
forward_quote = false        # true: the forwarded text comes in
                             # quoted with indent_string
abort_nosubject = "ask-yes"  # empty subject: "yes" aborts without
                             # asking, "no" never asks
abort_unmodified = true      # false: keep a draft the first editor
                             # pass left untouched
mark_old = true              # unread mail ages to old (O) when you
                             # leave the mailbox, as in mutt
delete_untag = true          # deleting a tagged message untags it
flag_safe = false            # true: a flagged message cannot be
                             # deleted (mutt's $flag_safe)
maildir_trash = false        # true: a purge writes the maildir T flag
                             # instead of unlinking (maildir only)
mail_check_recent = true     # false announces any mailbox holding new
                             # mail, once, not only one that grew
check_new = true             # false stops rescanning the open maildir
sort_alias = "address"       # address completion order: address (the
                             # default), alias (by nick); reverse-
shell = "/bin/zsh"           # what a bare ! runs ($SHELL, then sh)
tmpdir = "~/tmp"             # where temporary files go ($TMPDIR, /tmp)
ispell = "aspell"            # the compose menu's i: `ispell -x FILE`
print_confirm = "ask-no"     # mutt's $print: "ask-yes" makes Enter
                             # print, "yes" never asks, "no" refuses

[index]
format = "%4C %Z %-6d %-15.15L (%?l?%4l&%4c?) %s"   # mutt's default
                                         # %Z status/flag/mark, where
                                         # the mark is mutt's to_chars:
                                         # + sole recipient, T one of
                                         # several, C on the Cc, F sent
                                         # by me, L to a subscribed list
                                         # %F from (%L: "To <list>" for
                                         # List-Id mail) %c size %l body
                                         # lines %M collapsed count
                                         # %s subject, and
                                         # %?X?then&else? conditionals
sort = "threads"             # initial sort; reverse-date, size, ...
sort_aux = "last-date-sent"  # which thread comes first: last- by its
                             # newest message, reverse- newest thread
                             # first (mutt's spellings)
date_format = "%d.%m.%Y"     # strftime for the date column
hide_thread_subject = false  # true blanks a thread reply's subject
                             # when it repeats the parent's (mutt's)
uncollapse_new = true        # a folded thread unfolds when it grows

[pager]
index_lines = 10             # keep a slice of the index above the pager
context = 3                  # overlapping lines when paging
search_context = 0           # lines kept above a search hit (mutt's)
reflow_text = true           # false: keep a format=flowed part's own
                             # line breaks instead of rewrapping it
alternative_order = ["text/plain", "text/html"]
                             # which part of a multipart/alternative
                             # shows, most wanted first ("text/*" ok)

[filters]                    # auto_view: render a part via a command
"text/html" = "w3m -dump -T text/html -O UTF-8"
"text/calendar" = ""         # empty: take the command from mailcap

[net]                        # how long the network gets before rmut
connect_timeout = 10         # says so; 0 waits as long as the OS
timeout = 30                 # does (about two minutes). timeout is
                             # data on a live connection, never off:
                             # IMAP IDLE ticks on it, minimum 5
system_cas = true            # trust the OS cert store too (mutt's
                             # $ssl_usesystemcerts); adds to the roots
certificate_file = "~/.mutt/certs.pem"  # a PEM of extra roots to
                             # trust (a private/self-signed CA)

[ui]
theme = "default"            # or "mono"
status_format = "---rmut: %f [Msgs:%?M?%M/?%m New:%n%?d? Del:%d?] (sort:%s)%?V? (limit:%V)?"
                             # bottom line: %f mailbox %m msgs
                             # %M shown-when-limited %n new %u unread
                             # %d deleted %F flagged %t tagged %s sort
                             # %V limit %r pending-mark %v version,
                             # with %?X?then&else? conditionals
beep = true                  # ring the bell on an error (mutt's $beep)
beep_new = false             # true: ring when mail arrives, too
wait_key = true              # a shell escape (!) ends with "Press
                             # Enter", so its output can be read
set_title = false            # true sets the terminal title (mutt's
                             # $ts_enabled)
title_format = "rmut: %f"    # its format, the status specifiers again
history_file = "~/.rmut_history"  # persist prompt history across
                             # sessions (mutt's $history_file); unset
                             # keeps it in-memory only
hyperlinks = true            # the pager's URLs as OSC 8 links; false
                             # for a terminal that prints them
url_command = "xdg-open"     # opens a URL from Ctrl+B's list (%s, or
                             # the URL appended)
status_on_top = false        # true puts the status bar near the top
arrow_cursor = false         # true marks the selection with -> not
                             # reverse video (mutt's $arrow_cursor)
menu_scroll = true           # false turns a page when the cursor
                             # leaves the screen (mutt's default)
menu_context = 0             # lines kept in view past the cursor
menu_move_off = true         # false keeps the last message on the
                             # bottom row once the index fills
help = true                  # false drops the key-help top line
error_history = 30           # what error-history shows; 0 disables
sort_browser = "alpha"       # folder browser order: alpha, count,
                             # unread, date, unsorted; reverse- flips
status_chars = "-*%"         # mutt's $status_chars: %r marker for
                             # unchanged/changed/read-only (unset keeps
                             # rmut's nothing/*/%)

[[color_index]]              # mutt's `color index FG BG PATTERN`:
pattern = "~f boss@example.com"   # any limit/search pattern; first
fg = "yellow"                # matching rule colors the index line,
# bg = "blue"                # over the [colors] slots below

[sidebar]                    # left pane: mail.mailboxes with new-mail
visible = false              # counts (B toggles at runtime; bold =
width = 24                   # has new mail, > marks the open one)

[colors]                     # status_fg status_bg deleted flagged
deleted = "red"              # tagged header. One colour each: for
                             # mutt's `color index black magenta ~D`
                             # (a painted bar) use a [[color_index]]
                             # rule, which takes fg and bg both

[keys.index]                 # remap: action = "key" (see ? for actions)
sync = "w"
[keys.pager]

[macros.index]               # macro: key = "replayed key sequence",
L = "l~f jane<enter>"        # literals + <enter>/<esc>/<ctrl+x>/...;
"." = "/~D<enter>"           # hop to the next message marked deleted,
"," = "<alt+/>~D<enter>"     # and back again
[macros.pager]               # it feeds the input queue, so it can
                             # drive prompts; a macro shadows a
                             # binding on the same key (like mutt)

[[accounts]]                 # remote account: open with `rmut imap:work`
name = "work"
user = "jane@example.com"
password_command = "pass show mail/work"   # first stdout line
# password = "..."                         # alternative, but see Credentials below
# auth = "xoauth2"                         # or "oauthbearer": OAuth2 with
# token_command = "oauth2ms"               # a fresh access token per connection
imap_host = "imap.example.com"             # imap_port = 993 (implicit TLS; 143 = STARTTLS)
smtp_host = "smtp.example.com"             # smtp_port = 587 (STARTTLS; 465 = implicit TLS)
sent_folder = "Sent"                       # Fcc target via IMAP APPEND
identity = { name = "Jane W", email = "jane@work.example.com" }  # From
                                           # when composing from this account

[pgp]                        # optional; gpg from $PATH by default
command = "gpg"
sign_key = "jane@example.com"  # --local-user; gpg's default key if unset
sign_by_default = false        # preselect security for new drafts
encrypt_by_default = false
reply_sign = false             # a reply to signed mail defaults signed
reply_encrypt = false          # a reply to encrypted mail, encrypted
reply_sign_encrypted = false   # a reply to encrypted mail, signed too
```

Key syntax: a character, `ctrl+x`, `alt+x`, or enter/esc/space/tab/
backspace/up/down/pgup/pgdn/home/end. `?` lists all actions with their
current keys. An `alt+x` binding also answers to mutt's spelling, Esc
then x: in the index, the pager and the compose menu an Esc waits for
the next key, so Esc / is search-reverse however slowly it is typed,
and Esc Esc is whatever Esc alone is bound to (back, in the pager).

### Coming from mutt

```sh
rmut --import-muttrc -w            # reads ~/.muttrc, writes the config
rmut --import-muttrc               # or print it, to look first
```

`-w` saves it to `~/.config/rmut/config.toml`, creating the directory
and refusing to overwrite a config that is already there. Any `alias`
lines it found, including ones in a `source`d file, are written to
`~/.config/rmut/aliases` at the same time: rmut keeps aliases in a
mutt-format file of their own rather than in the TOML, so your mutt
alias file works as it stands (point `$RMUT_ALIASES` at it if you
would rather keep it where it is), and `a` in the index appends to
it. Without `-w` the translation goes to stdout for review, aliases
as a comment block, so redirect it yourself if that is what you
want.

The translation covers identity, folder/mailboxes, record/postponed,
sendmail/editor/print_command/query_command/status_format, binds,
status/header colors and `color index FG BG PATTERN` rules, PGP
defaults, IMAP/SMTP URLs into an `[[accounts]]` skeleton, with
`auth`/`token_command` when `*_authenticators` names
oauthbearer/xoauth2, reverse_name, alternates/unalternates,
my_hdr/unmy_hdr, text_flowed/reflow_text,
auto_view/unauto_view (the command left to mailcap),
alternative_order/unalternative_order,
folder-hooks/send-hooks that only set from/realname into
`[[identities]]` rules, every other folder-hook plus message-hook,
reply-hook, fcc-hook/fcc-save-hook and crypt-hook into their own hook
tables, and macros whose sequence is plain keys and
prompt input) into rmut
TOML on stdout for review; it never writes any file itself.
Directives with no rmut equivalent are kept as `# not imported:`
comments, and ones that match rmut's built-in behavior (ssl_starttls,
UTF-8 charset, pgp_auto_decode, ...) are acknowledged under
`# satisfied by rmut's defaults`; `imap_pass`/`smtp_pass` become the
account's stored `password`. Alias files need no
translation: rmut reads mutt-format aliases, so point `$RMUT_ALIASES`
at your existing file or copy it to `~/.config/rmut/aliases`.

## Development

```sh
just test    # cargo test --workspace
just lint    # clippy -D warnings + fmt --check
just e2e     # pty-driven end-to-end tests
just check   # test + lint + e2e
```

Tests sit where what they test does. `rmut-core` tests parsing and
protocols, `rmut-session` tests the operations against a maildir in a
tempdir and asserts on values (`crates/rmut-session/src/tests.rs`),
and the pty suite in `tests/e2e` drives a real terminal for the keys,
the drawing, and the paths that go all the way out through sendmail,
IMAP and gpg.

Three crates. `rmut-core` is the mail itself: maildir, mbox, IMAP,
SMTP, compose, PGP, patterns, threading, the importer. `rmut-session`
is an open mailbox and everything that can be done to it, with no
screen attached: what is in it, what is selected, marks, sync, save
and copy, the undo stack, the outbox, the hooks, and the named
functions a keymap binds. `rmut` (the binary crate) owns the menus, the keys, the
theme and the drawing, and drives a session.

An operation reports what it did as a `notice::Notice` rather than
writing into a status field, and the front end installs the sink that
receives it: the terminal keeps the last notice for its message line,
a test reads the values. A session with no sink installed is silent.

When an operation needs an answer it hands back an `Ask` ("Save to
mailbox: ", "Purge 3 deleted message(s)? (y/n): ") rather than opening
a prompt, and the front end answers with the `AskKind` it came with;
answering can produce the next question. What only a front end can do
comes back as a `Request`: quit, run an editor or a shell command,
suspend, show the draft again. A front end that cannot do one of them
simply does not honour it.

Every index operation has the name a muttrc binds (`delete-message`,
`group-reply`, `tag-prefix`), and the names live with the operations
in `rmut-session`. A front end resolves whatever it has (a keystroke,
a menu item, `:exec`) to a `Function`, hands it to
`Session::run_function`, and reads the `Outcome`: done, a question to
put, or a `FrontOp` naming the one thing it has to do itself. The key
tables stay in the front end, since only it knows what a key is. See
[docs/PLAN.md](docs/PLAN.md) for where that line is headed.