S-NAIL(1)

๐Ÿ’Œ S-NAIL(1) ๐Ÿ’Œ

Heyyy bestie, welcome to the S-nail manual, the General Commands section, aka section 1 ๐ŸŽ€ Let's get into it.


โœจ NAME โœจ

Okay so the whole NAME is S-nail [v14.9.25] โ€” send and receive Internet mail ๐Ÿ“ฌ It's literally a mail client for sending and receiving Internet mail, and it's giving main character energy ๐Ÿ’…

๐Ÿฆ‹ SYNOPSIS ๐Ÿฆ‹

Here's how you can call it. There are three main ways, plus the two lil help flags ๐Ÿงš

s-nail [-DdEFinv~#] [-: spec] [-A account] [:-a attachment:]
       [:-b bcc-addr:] [:-C "field: body":] [:-c cc-addr:]
       [-M type | -m file | -q file | -t] [-r from-addr]
       [:-S var[=value]:] [-s subject] [:-T "field: addr":] [:-X cmd:]
       [:-Y cmd:] [-.] :to-addr: [-- :mta-option:]

s-nail [-DdEeHiNnRv~#] [-: spec] [-A account] [:-C "field: body":]
       [-L spec] [-r from-addr] [:-S var[=value]:] [-u user] [:-X cmd:]
       [:-Y cmd:] [-- :mta-option:]
s-nail [-DdEeHiNnRv~#] [-: spec] [-A account] [:-C "field: body":] -f
       [-L spec] [-r from-addr] [:-S var[=value]:] [:-X cmd:] [:-Y cmd:]
       [file] [-- :mta-option:]

s-nail -h | --help
s-nail -V | --version

๐Ÿ“– DESCRIPTION ๐Ÿ“–

Okay, big news first ๐Ÿ‘€ S-nail (S-nail) is about to get major changes in v15.0 (circa 2022). Some backward incompatibilities can't be avoided, sorry bestie ๐Ÿ˜ญ The โ€œCOMMANDSโ€ are changing to โ€œShell-style argument quotingโ€, and shell metacharacters are going to become (more) meaningful. Some commands already accept new syntax today via wysh (โ€œCommand modifiersโ€). Behaviour gets flagged as [v15-compat] and [no v15-compat]. Setting v15-compat (โ€œINTERNAL VARIABLESโ€) will pick the new behaviour when it applies, and giving it a value makes wysh an implied default. Anything marked [Obsolete] is on its way out ๐Ÿฅ€

Warning! v15-compat (with value) will be a default in v14.10.0! ๐Ÿšจ

S-nail gives you a simple and friendly space for sending and receiving mail ๐ŸŒธ It's meant to do everything the POSIX mailx(1) command does, but it's MIME capable, and it optionally adds extras like line editing, S/MIME, SMTP and POP3, among others. It splits your incoming mail into its individual messages so you can deal with them in any order you want ๐Ÿฆ‹ It has loads of โ€œCOMMANDSโ€ and โ€œINTERNAL VARIABLESโ€ for managing messages and sending mail. There's also simple editing to help you compose outgoing messages, plus steadily more powerful and reliable non-interactive scripting ๐Ÿงš

๐ŸŽ€ Options ๐ŸŽ€

  • -: spec, --resource-files=.. โ€” Controls which โ€œResource filesโ€ get loaded (as via source) ๐Ÿ“‚ spec is parsed case-insensitively. The letter s means the system wide s-nail.rc, and u means your personal ~/.mailrc. The original system wide resource is compiled in too, and you can reach it with x. - and / turn resource files off. Order matters, and the default is su. This option overrides -n.
  • -A name, --account=.. โ€” Activates the user account name once startup is finished (resource files loaded, and only -X commands get run) โœจ It then switches to its โ€œprimary system mailboxโ€ (most likely the inbox). If activation fails, the program exits when it's used non-interactively, or if either errexit or posix is set.
  • -a file[=input-charset[#output-charset]], --attach=.. โ€” (Send mode) Attaches file ๐Ÿ“Ž For (Compose mode) options, check out ~@ and ~^. file gets tilde expansion (see โ€œFilename transformationsโ€ and folder). If it isn't accessible but has a = in it, then anything before the last = is used as the filename, and anything after it is used as a character set specification, as shown.

If you only specify an input character set, the input side is fixed and no character set conversion happens. An empty value or the special string - gets taken as ttycharset (the default). If you also specify an output character set, the conversion you asked for happens right away, no matter the file type and content. The exception is an empty string or -, which picks the default conversion algorithm (see โ€œCharacter setsโ€). In that case there's no immediate conversion, and file plus its contents get MIME-classified first (โ€œHTML mail and MIME attachments, The mime.types filesโ€). Only the latter mode is available unless features includes ,+iconv, ๐Ÿ’…

  • -B โ€” ([Obsolete]: S-nail always uses line-buffered output ๐Ÿฅ€ If you want line-buffered input even in batch mode, enable batch mode with -#.)
  • -b addr, --bcc=.. โ€” (Send mode) Sends a blind carbon copy to the recipient addr ๐Ÿ™ˆ You can use this more than once. Also see the section โ€œOn sending mail, and non-interactive modeโ€.
  • -C "field: body", --custom-header=.. โ€” Creates a custom header that sticks around for the whole session ๐ŸŽ€ A custom header is the field name, then a colon :, then the field content body. For example: -C "Blah: Neminem laede; imo omnes, quantum potes, juva". Standard header field names can't be overwritten by custom headers. Runtime adjustable custom headers live in the variable customhdr. In (Compose mode), ~^ (one of the โ€œCOMMAND ESCAPESโ€) as well as digmsg are the most flexible and powerful ways to manage message headers ๐Ÿ’œ You can use this option more than once.
  • -c addr, --cc=.. โ€” (Send mode) Just like -b, except it puts the argument in the list of carbon copies ๐Ÿ’Œ
  • -D, --disconnected โ€” [Option] Starts up with disconnected set ๐Ÿ”Œ
  • -d, --debug โ€” Enters a debug-only sandbox mode by setting the internal variable debug ๐Ÿงช You can get the same thing with -S debug or set debug. Also see -v.
  • -E, --discard-empty-messages โ€” (Send mode) Sets skipemptybody, so messages with an empty message part body get discarded (successfully, bestie) ๐Ÿ—‘๏ธ
  • -e, --check-and-exit โ€” Just checks whether mail is there (in the system inbox, or the one you picked with -f) ๐Ÿ‘€ If yes, it returns an exit status of zero. If not, it returns a non-zero value. To narrow down which mails count in this check, add a message specification with -L. Quickrun: it doesn't open an interactive session.
  • -F โ€” (Send mode) Saves the message you're sending into a file named after the local part of the first recipient's address, instead of into record ๐Ÿ“
  • -f, --file โ€” Reads the contents of your โ€œsecondary mailboxโ€ MBOX (or the file you specify) so it can be processed ๐Ÿ“ฌ When you quit S-nail, it writes your undeleted messages back to that file (but be aware of the hold option ๐Ÿ‘€). The optional file argument goes through some special โ€œFilename transformationsโ€ (as via folder). Note that file isn't an argument to the flag -f. It's taken from the command line after option processing is finished. If you want to use a file that starts with a hyphen-minus, prefix it with a relative path, like ./-hyphenbox.mbox ๐Ÿงš
  • -H, --header-summary โ€” Displays a summary of headers for the given folder (depending on -u, the inbox or MAIL, or whatever you set with -f), then exits ๐Ÿ’… A configurable summary view is available via -L. This mode doesn't honour showlast. Quickrun: it doesn't open an interactive session.
  • -h, --help โ€” Shows a brief usage summary ๐Ÿซถ Use --long-help for a list of long options.
  • -i โ€” Sets ignore so tty interrupt signals get ignored ๐Ÿ™…โ€โ™€๏ธ
  • -L spec, --search=.. โ€” Displays a summary of headers for every message that matches the given spec, in the folder found by the same algorithm -H uses, then exits ๐Ÿ” See the section โ€œSpecifying messagesโ€ for the format of spec. This mode doesn't honour showlast.

If you also give -e, no header summary gets produced. Instead, S-nail tells you through its exit status whether spec matched any messages (0) or none (1). Any verbose output is suppressed in this mode, so if you want it, you have to turn it on explicitly (see -v). Quickrun: it doesn't open an interactive session.

  • -M type โ€” (Send mode) Flags standard input with the MIME Content-Type: set to the known type you give (โ€œHTML mail and MIME attachments, The mime.types filesโ€), and uses it as the main message body ๐Ÿ’– [v15 behaviour may differ] Using this option bypasses processing of message-inject-head and message-inject-tail. Also see -q, -m, -t.
  • -m file โ€” (Send mode) MIME classifies the file you specify and uses it as the main message body ๐ŸŽ€ [v15 behaviour may differ] Using this option bypasses processing of message-inject-head and message-inject-tail. Also see -q, -M, -t.
  • -N, --no-header-summary โ€” Stops the initial display of message headers when you read mail or edit a mailbox folder, by calling unset on the internal variable header ๐Ÿ™ˆ
  • -n โ€” Standard flag that stops the system wide s-nail.rc from being read at startup ๐ŸŒ™ The -: option gives you more control over the startup sequence. Also see โ€œResource filesโ€.
  • -q file, --quote-file=.. โ€” (Send mode) Starts the message body off with the contents of file ๐Ÿ“ file can be standard input -, but only in a non-interactive context. Also see -M, -m, -t.
  • -R, --read-only โ€” Any mailbox folder (aka folder) you open will be in read-only mode ๐Ÿ‘€๐Ÿ”’
  • -r from-addr, --from-address=.. โ€” The RFC 5321 reverse-path is what's used for relaying and delegating messages to their destination(s), like when reporting delivery errors ๐Ÿ“ฎ It's normally derived from the address in the from header (or, if that has multiple addresses, from sender). A file-based aka local executable mta (Mail-Transfer-Agent) instead uses the local identity of the user who starts things up.

When you use this one, the single addressee from-addr gets assigned to the internal variable from ๐Ÿ’Œ. But that's not all, bestie! -f from-addr also gets passed along to a file-based mta every single time a message goes out ๐Ÿš€. And if from-addr includes a user name, the address components get split up and the name part is handed to the file-based mta on its own via -F name ๐ŸŽ€. Even though it isn't even a recipient, the shquote expandaddr flag is still supported ๐Ÿ’….

If you pass an empty string as from-addr, the content of the variable from (or, if that holds multiple addresses, sender) gets evaluated and used for this purpose whenever the file-based mta is contacted ๐Ÿงš. By default, without -r that is, neither -f nor -F command line options get used when contacting a file-based MTA, unless this automatic deduction is enforced by setting the internal variable r-option-implicit ๐Ÿฆ‹.

๐Ÿ™ˆ Remarks: okay this part is serious. Many default installations and sites won't let you override the local user identity like this unless either the MTA has been configured accordingly or the user is a member of a group with special privileges. And passing an invalid address will cause an error.
  • -S var[=value], --set=.. โ€” Set (or, with the prefix string no, as documented in "INTERNAL VARIABLES", unset) a variable and optionally assign it a value, if supported ๐Ÿ”ฅ. [v15 behaviour may differ] The whole expression is evaluated as if it were written within dollar-single-quotes (see "Shell-style argument quoting") if the internal variable v15-compat is set. If the operation fails, the program will exit if any of errexit or posix are set. Settings made via -S can't be changed from within "Resource files" or from an account switch initiated by -A. They become mutable again before commands registered via -X are executed ๐Ÿ‘‘.
  • -s subject, --subject=.. โ€” (Send mode) Sets the subject of the message you're sending. Newline (NL) and carriage-return (CR) bytes are invalid and will be normalized to space (SP) characters ๐Ÿ’Œ.
  • -T "field: addr", --target=.. โ€” (Send mode) Adds addr to the list of receivers targeted by field. For now the only supported fields are bcc, cc, fcc, and to ๐ŸŽฏ. The field and the body (address) are separated by a colon : and optionally some blank (space, tabulator) characters. The shquote expandaddr flag is supported. addr gets parsed like a message header address line, as if it were part of a template message fed in via -t, and the same modifier suffix is supported too. This option may be used multiple times ๐Ÿฆ‹.
  • -t, --template โ€” (Send mode) The text message given on standard input is expected to contain one or multiple plain text message headers, separated from the message body by an empty line ๐Ÿ“. [v15 behaviour may differ] Readily prepared MIME mail messages can't be passed, sorry bestie ๐Ÿ™ˆ. Headers can span multiple consecutive lines if the follow lines start with any amount of whitespace. A line starting with the number sign # in the first column is ignored ๐ŸŒธ. Message recipients can be given via the message headers To:, Cc:, Bcc: (the ?single modifier enforces treatment as a single addressee, for example To?single: exa, <m@ple>) or Fcc:. They'll be added to any recipients specified on the command line, and they're likewise subject to expandaddr validity checks. If a message subject is specified via Subject:, that one gets used in favour of the one given on the command line โœจ.

More optional headers are Reply-To: (possibly overriding reply-to), Sender: (sender), and From: (from and / or option -r). Message-ID:, In-Reply-To:, References: and Mail-Followup-To: are, by default, created automatically depending on message context, and they'll be used if you specify them (though a special address massage will still occur for the latter) ๐Ÿงš. Any other custom header field (also see -C, customhdr and ~^) is passed through entirely unchanged. In conjunction with the options -~ or -# it's possible to embed "COMMAND ESCAPES" ๐Ÿ’œ. Also see -M, -m, -q.

  • -u user, --inbox-of=.. โ€” Initially reads the "primary system mailbox" of user, appropriate privileges presumed. This is effectively identical to -f %user ๐Ÿ“ฌ.
  • -V, --version โ€” Shows the S-nails version and exits ๐ŸŒˆ. The version command also shows the list of features: $ s-nail -:/ -Xversion -Xx.
  • -v, --verbose โ€” Sets the internal variable verbose to enable logging of informational context messages. Using it multiple times increases the level of verbosity ๐Ÿ”Š. Also see -d.
  • -X cmd, --startup-cmd=.. โ€” Adds the given cmd (or multiple, for a multiline argument) to a list of commands to be executed before normal operation starts โญ. The commands get evaluated as a unit, just as via source. Correlates with -# and errexit.
  • -Y cmd, --cmd=.. โ€” Adds the given cmd (or multiple, for a multiline argument) to a list of commands to be executed after normal operation has started ๐ŸŽ€. The commands get evaluated successively in the given order, as if they were given on the program's standard input. That means before interactive prompting begins in interactive mode, and after standard input has been consumed otherwise ๐Ÿ’….
  • -~, --enable-cmd-escapes โ€” Enables "COMMAND ESCAPES" in (Compose mode) even in non-interactive use cases ๐Ÿซถ. This can, for example, be used to automatically format the composed message text before sending the message:
$ ( echo 'line    one. Word.     Word2.';\
    echo '~| /usr/bin/fmt -tuw66' ) |\
  LC_ALL=C s-nail -d~:/ -Sttycharset=utf-8 bob@exam.ple
  • -#, --batch-mode โ€” Enables batch mode, and honestly it's giving automation ๐Ÿค–. Standard input is made line buffered, the complete set of (interactive) commands is available, and processing of "COMMAND ESCAPES" is enabled in "Compose mode". Diverse "INTERNAL VARIABLES" get adjusted for batch necessities, exactly as if done via -S: emptystart, noerrexit, noheader, noposix, quiet, sendwait, typescript-mode, as well as MAIL, MBOX and inbox (the latter three to /dev/null). Also, the values of COLUMNS and LINES are looked up and acted upon ๐Ÿ’€. The following prepares an email message in a batched dry run:
$ for name in bob alice@exam.ple lisa@exam.ple; do
    printf 'mail %s\n~s ubject\nText\n~.\n' "${name}"
  done |
  LC_ALL=C s-nail -#:x -Smta=test \
    -X'alias bob bob@exam.ple'
  • -., --end-options โ€” This flag forces termination of option processing in order to prevent "option injection" (attacks) ๐Ÿ›ก๏ธ. It also forcefully puts S-nail into send mode, see "On sending mail, and non-interactive mode" ๐Ÿซก.

If the setting of expandargv allows their recognition, all mta-option arguments given at the end of the command line after a -- separator will be passed through to a file-based mta (Mail-Transfer-Agent) and persist for the entire session ๐Ÿงš. expandargv constraints do not apply to the content of mta-arguments. Command line receiver address handling supports the shquote constraint of expandaddr. For more, please see "On sending mail, and non-interactive mode" ๐ŸŒธ.

$ s-nail -#:/ -X 'addrcodec enc Hey, ho <silver@go>' -Xx

๐ŸŒธ A starter ๐ŸŒธ

S-nail is a direct descendant of BSD Mail, which itself is a successor to the Research Unix mail that "was there from the start" according to "HISTORY" ๐Ÿ‘‘. It thus represents the user side of the Unix mail system. The system side (Mail-Transfer-Agent, MTA) was traditionally taken by sendmail(8), and most MTAs provide a binary of this name for compatibility reasons ๐Ÿ’…. If the [Option]al SMTP mta is included in the features of S-nail, then the system side isn't a mandatory precondition for mail delivery ๐Ÿฆ‹.

S-nail strives for compliance with the POSIX mailx(1) standard, but posix, one of the "INTERNAL VARIABLES", or its "ENVIRONMENT"al equivalent POSIXLY_CORRECT, needs to be set to adjust behaviour to be almost on par. Almost, because there's one important difference: POSIX "Shell-style argument quoting" is ([v15 behaviour may differ] increasingly) used instead of the "Old-style argument quoting" that the standard documents, which is believed to be a feature ๐ŸŽ€. The builtin as well as the (default) global s-nail.rc "Resource files" already bend the standard-imposed settings a bit ๐ŸŒˆ.

For example, hold and keepsave are set in order to suppress the automatic moving of messages to the "secondary mailbox" MBOX that would otherwise occur (see "Message states"). keep is set to not remove empty system MBOX mailbox files (or all empty such files in posix mode), so file permissions don't get mangled when files eventually get recreated ๐Ÿงน.

To enter interactive mode even if the initial mailbox is empty, emptystart is set. editheaders allows editing of headers, and fullnames stops addresses from being stripped down in "Compose mode" ๐Ÿ’–. quote includes the message being responded to when replying, and it's indented by an indentprefix, which also deviates from the standard-imposed settings โœจ. mime-counter-evidence is fully enabled, too. It sets followup-to-honour and reply-to-honour to comply with reply address desires ๐Ÿซถ.

Credentials and other settings are easily addressable by grouping them via account ๐Ÿ”. The file mode creation mask can be managed with umask ๐Ÿ—‚๏ธ. Files and shell pipe output can be sourced for evaluation, also during startup from within the "Resource files" ๐Ÿงš. Informational context can be available by setting verbose or debug (as via -v, -d) ๐Ÿ”.

๐Ÿ“ฌ On sending mail, and non-interactive mode

okay bestie, so if you want to send a message to one or more people, S-nail hands the actual delivery to a local or built-in mta (Mail-Transfer-Agent) transport. You just pass the names of the people as arguments, and use -b and -c to add (blind) carbon copy receivers ๐Ÿ’Œโœจ

# Via test MTA
$ echo Hello, world | s-nail -:/ -Smta=test -s test $LOGNAME

# Via sendmail(1) MTA
$ </dev/null s-nail -:x -s test $LOGNAME

# Debug dry-run mode:
$ </dev/null LC_ALL=C s-nail -d -:/ \
   -Sttycharset=utf8 -Sfullnames \
   -b bcc@exam.ple -c cc@exam.ple -. \
   '(Lovely) Bob <bob@exam.ple>' eric@exam.ple

# With SMTP (no real sending due to -d debug dry-run)
$ LC_ALL=C s-nail -d -:/ -Sv15-compat -Sttycharset=utf8 \
    -S mta=smtps://mylogin@exam.ple:465 -Ssmtp-auth=none \
    -S from=scriptreply@exam.ple \
    -a /etc/mail.rc --end-options \
    eric@exam.ple < /tmp/letter.txt

Email addresses and plain user names go through alternates filtering, and names on their own get expanded through alias and mta-aliases first ๐Ÿฆ‹ An address in angle brackets that is only a valid local user, like <name>, gets turned into a fully qualified address if hostname is unset or set to a non-empty value. If hostname is set to the empty value, the conversion is left up to the mta ๐Ÿซถ Setting expandaddr gives you fine-grained control over recipient address types other than user names and network addresses, so you can be picky about it ๐Ÿ’…

Recipients get sorted into types like this, and it is literally so specific ๐Ÿ˜ญ

  • Any name starting with a vertical bar | is a command pipe: the command string after the | gets executed, and the message is sent to its standard input.
  • Any name that is only a hyphen-minus -, or starts with a solidus / or the dot solidus ./, is treated as a file, no matter what else is in there.
  • Any other name containing a commercial at @ is a network address.
  • Any other name starting with a plus sign + is a mailbox name.
  • Any other name that contains a solidus /, but has no exclamation mark ! or percent sign % character before it, is also a mailbox name.
  • Whatever is left over is treated as a network address.

You can skip this whole classification thing by using a Fcc: header, see โ€œCompose modeโ€ ๐ŸŒธ

$ echo bla | s-nail -Sexpandaddr -s test ./mbox.mbox
$ echo bla | s-nail -Sexpandaddr -s test '|cat >> ./mbox.mbox'
$ echo safe | LC_ALL=C \
    s-nail -:/ -Smta=test -Sv15-compat -Sttycharset=utf8 \
      --set mime-force-sendout --set fullnames \
      -S expandaddr=fail,-all,+addr,failinvaddr -s test \
      --end-options 'Imagine John <cold@turk.ey>'

Before a message gets sent, it goes through editing in โ€œCompose modeโ€ โœ๏ธ A lot of settings are static, though, and can be set more generally ๐Ÿ‘‘ The envelope sender address is defined by from, and explicitly setting an originating hostname might be a vibe, especially with the built-in SMTP Mail-Transfer-Agent mta ๐ŸŒˆ โ€œCharacter setsโ€ for outgoing message and MIME part content are configurable via sendcharsets, while input data is assumed to be in ttycharset. Message data gets passed over the wire in a mime-encoding, and MIME parts (aka attachments) need a mimetype, which is usually taken out of โ€œThe mime.types filesโ€ ๐Ÿ“Ž Saving copies of sent messages in a record mailbox might be desirable too. Just know that for most mailbox folder targets, โ€œFilename transformationsโ€ will be performed ๐Ÿงš

For arranging a whole environment of settings that you can switch to with one single command or command line option, there are accounts ๐Ÿ’œ Or you can go with a flat configuration, using so-called variable chains. These automatically pick โ€˜USER@HOSTโ€™ or โ€˜HOSTโ€™ context-dependent variants that some variables support. For example, addressing โ€˜Folder pop3://yaa@exam.pleโ€™ would find pop3-no-apop-yaa@exam.ple, pop3-no-apop-exam.ple and pop3-no-apop, in that order ๐Ÿ“ For more, please see โ€œOn URL syntax and credential lookupโ€ and โ€œINTERNAL VARIABLESโ€ โœจ

To avoid environmental noise, scripts should create a script-local environment. Ideally, use the command line options -: to disable configuration files, together with repetitions of -S to specify variables ๐Ÿซถ

$ env LC_ALL=C s-nail -:/ \
    -Sv15-compat \
    -Sttycharset=utf-8 -Smime-force-sendout \
    -Sexpandaddr=fail,-all,failinvaddr \
    -S mta=smtps://mylogin@exam.ple:465 -Ssmtp-auth=login \
    -S from=scriptreply@exam.ple \
    -s 'Subject to go' -a attachment_file \
    -Sfullnames -. \
    'Recipient 1 <rec1@exam.ple>' rec2@exam.ple \
    < content_file

As shown, scripts that produce messages can โ€œfakeโ€ a locale environment ๐ŸŽญ The example above sets the all-compatible 7-bit clean LC_ALL โ€œCโ€, but it will still take and send UTF-8 in the message text by using ttycharset ๐Ÿ’– If character set conversion is compiled in (features includes the term ,+iconv,), invalid character input data (according to ttycharset) would normally cause errors. Setting mime-force-sendout will instead, as a last resort, classify the input as binary data, so message creation can still succeed ๐Ÿ™ˆ (Such content can then be inspected either by installing a pipe-TYPE/SUBTYPE handler for โ€˜application/octet-streamโ€™, or possibly automatically through mime-counter-evidence.)

In interactive mode, which we'll get into soon, you can send messages by calling the mail command with a list of recipient addresses ๐Ÿ’…

$ s-nail -:/ -Squiet -Semptystart -Sfullnames -Smta=test
"/var/spool/mail/user": 0 messages
? mail "Recipient 1 <rec1@exam.ple>", rec2@exam.ple
...
? # Will do the right thing (tm)
? m rec1@exam.ple rec2@exam.ple

๐ŸŽ€ Compose mode

If standard input is a terminal instead of the message you want to send, you're expected to type in the message contents yourself โŒจ๏ธ In compose mode, lines beginning with the character ~ (in fact the value of escape) are special. These are so-called โ€œCOMMAND ESCAPESโ€ and they can read in files, process shell commands, add and edit attachments and more ๐Ÿ”ฅ For example, ~v or ~e will start the VISUAL text EDITOR, respectively, so you can revise the message in its current state. ~h lets you edit the most important message headers, and with the potent ~^ you can create custom headers, for example (more specifically than with -C and customhdr) ๐Ÿ’œ Optionally, ~? gives an overview of most of the other available command escapes ๐ŸŒŸ

To create file-carbon-copies, the special recipient header Fcc: can be used as often as you like, for example via ~^ ๐Ÿฆ‹ Its entire value (or body, in standard terms) is interpreted as a folder target, after being subject to โ€œFilename transformationsโ€. This is the only way to create a file-carbon-copy without introducing an ambiguity about how the address is interpreted, so file names with leading vertical bars or commercial ats can be used here ๐Ÿ’… Like all other recipients, Fcc: is subject to the checks of expandaddr. Any local file and pipe command addressee honours the setting of mbox-fcc-and-pcc ๐Ÿซถ

Once you're done editing, the command escape ~. (see there) will call hooks, insert automatic injections and receivers, leave compose mode and send the message once it is completed ๐Ÿ“จ Want to abort instead? Use either ~x or ~q. The latter saves the message in the file denoted by DEAD, unless nosave is set ๐Ÿ˜ญ And unless ignoreeof is set, the effect of ~. can also be achieved by typing end-of-transmission (EOT) via โ€˜control-Dโ€™ (โ€˜^Dโ€™) at the beginning of an empty line. ~q is always reachable by typing end-of-text (ETX) twice via โ€˜control-Cโ€™ (โ€˜^Cโ€™) ๐Ÿ’€

The compose mode hooks on-compose-enter, on-compose-splice, on-compose-leave and on-compose-cleanup can be set to defined macros. They give you reliable and increasingly powerful ways to make automated message adjustments based on message context, like adding message signatures (message-inject-head, message-inject-tail) or creating additional receiver lists (also by setting autocc, autobcc) ๐ŸŒˆ To do that, the command digmsg can be used to query and adjust the status of message(s) ๐Ÿง  The splice hook can also make use of โ€œCOMMAND ESCAPESโ€. ([v15 behaviour may differ] The compose mode hooks work for forward, mail, reply and variants. resend and Resend only provide the hooks on-resend-enter and on-resend-cleanup, which are pretty restricted due to the nature of the operation ๐Ÿ™ˆ)


๐Ÿ“– On reading mail, and more on interactive mode

When you invoke S-nail without addressees, it enters interactive mode, where you can read mails ๐Ÿ“ฌ In that case, the user's system inbox is read in (for more on mailbox types, please see the command folder). A one-line header of each message in there is displayed if the variable header is set โœจ The visual style of this header summary can be adjusted with the variable headline, and the possible sorting criterion can be set via autosort ๐ŸŽ€ You can scroll through screenfuls of headers with the command z ๐Ÿฆ‹ If the initially opened mailbox is empty, S-nail will instead exit immediately (after displaying a message), unless the variable emptystart is set ๐Ÿฅบ

At the prompt, the command list gives a listing of all available commands, and help optionally gives a summary of some common ones ๐Ÿ’… If the optional documentation strings are available (see features), you can type help X (or ?X) to see the actual expansion of X and what its purpose is. Commands can be abbreviated, which is so iconic. Just note that POSIX defines some abbreviations, so the alphabetical order of commands does not necessarily match the abbreviations. It is however possible to define overwrites with commandalias ๐ŸŒธ These commands can also produce more verbose output ๐Ÿ“ฃ

Messages are numbered (starting at 1), and those numbers uniquely identify each message ๐Ÿ”ข The current message, the โ€œdotโ€, will be either the first new message, or the first unread message, or the first message of the mailbox ๐ŸŽฏ The internal variable showlast will instead make the last message the one used for this purpose ๐Ÿ‘‘ The command headers displays a screenful of header summaries containing the โ€œdotโ€. The command from displays only the summaries of the given messages, defaulting to the โ€œdotโ€ ๐Ÿ’–

Okay bestie, so message content gets displayed with the command type (t, alias print) ๐Ÿ’Œ The variable crt decides whether and when S-nail uses your configured PAGER for display instead of writing straight to the terminal screen ๐Ÿ–ฅ๏ธ That's the only difference from more, which will always use the PAGER ๐Ÿ’… The command top only shows the first toplines lines of a message, and it might even squish them if topsqueeze is set ๐Ÿค Your display experience can get a glow-up by setting and adjusting mime-counter-evidence, and you should also check out โ€œHTML mail and MIME attachmentsโ€ โœจ

By default the current message (โ€œdotโ€) gets displayed, but like with a lot of other commands you can give a fancy message specification (see โ€œSpecifying messagesโ€) ๐Ÿง For example, t:u displays all unread messages, t. displays the โ€œdotโ€, t 1 5 types messages 1 and 5, t 1-5 types messages 1 through 5, and t- and t+ display the previous and the next message, respectively ๐Ÿฆ‹ The command search (a more substantial alias for from) displays a header summary of the given message specification list instead of their content. So if you want to search for subjects, it's giving:

? from '@Some subject to search for'

In the default setup all header fields of a message get typed, but you can white- or blacklist fields for a bunch of different situations using headerpick ๐ŸŽ€ For example, to restrict the display to a super restricted set for type: headerpick type retain from to cc subject ๐Ÿ’– To display all header fields regardless of the currently active ignore or retain lists, use the commands Type and Top. Show will show the raw message content ๐Ÿ™ˆ Note that historically the global s-nail.rc not only adjusts the list of displayed headers, but also sets crt. ([v15 behaviour may differ] A yet somewhat restricted) Reliable, scriptable message inspection is available via digmsg ๐Ÿค–

Dependent upon the configuration, a line editor (see the section โ€œOn terminal control and line editorโ€) aims at making the user experience with the many โ€œCOMMANDSโ€ a bit nicer โญ When reading the system inbox, or when -f (or folder) specified a mailbox explicitly prefixed with the special %: modifier (to propagate it to a โ€œprimary system mailboxโ€), then messages which have been read (see โ€œMessage statesโ€) will be automatically moved to a โ€œsecondary mailboxโ€, the user's MBOX file, when the mailbox is left. That happens either by changing the active mailbox or by quitting S-nail. This automatic move from a system- or primary- to the secondary mailbox is not performed when the variable hold is set ๐ŸŒธ Messages can also be explicitly moved to other mailboxes, whereas copy keeps the original message. write can be used to write out data content of specific parts of messages ๐Ÿ’œ

After examining a message, the user can reply r to the sender and all recipients (which will also be placed in To: unless recipients-in-cc is set), or Reply R exclusively to the sender(s) ๐Ÿซถ To comply with the receiver's desired reply address, the โ€œquadoptionโ€s followup-to-honour and reply-to-honour should usually be set. The commands Lreply and Lfollowup know how to apply a special addressee message, see โ€œMailing listsโ€. Dependent on the presence and value of quote, the message being replied to will be included in a quoted form. Forwarding a message will allow editing the new message: the original message will be contained in the message body, adjusted according to headerpick. It is possible to resend or Resend messages. The former will add a series of Resent- headers, whereas the latter will not. Different to newly created messages, editing is not possible, and no copy will be saved even with record unless the additional variable record-resent is set ๐Ÿงš When sending, replying or forwarding messages, comments and full names will be stripped from recipient addresses unless the internal variable fullnames is set.

Of course messages can be deleted d, and they can spring into existence again via undelete, or when the S-nail session is ended via the exit or xit commands to perform a quick program termination ๐ŸŒˆ To end a mail processing session regularly and perform a full program exit, one may issue the command quit. It will, among others, move read messages to the โ€œsecondary mailboxโ€ MBOX as necessary, discard deleted messages in the current mailbox, and update the line editor history-file (optional, see features). By the way, whenever the main event loop is about to look out for the next input line, it will trigger the hook on-main-loop-tick ๐Ÿ‘‘


โœจ HTML mail and MIME attachments โœจ

HTML-only messages are becoming more and more common, and many messages come bundled with a whole bouquet of MIME (Multipurpose Internet Mail Extensions) parts and attachments ๐ŸŒธ To get a notion of MIME types there is a built-in default set, onto which the content of โ€œThe mime.types filesโ€ will be added (as configured and allowed by mimetypes-load-control). Types can also become registered and listed with the command mimetype. To improve interaction with the faulty MIME part declarations of real life, mime-counter-evidence will allow verification of the given assertion, and the possible provision of an alternative, better MIME type ๐Ÿ™ƒ Note that plain text parts will always be preferred in multipart/alternative MIME messages unless mime-alternative-favour-rich is set.

Whereas a simple HTML-to-text filter for displaying HTML messages is optionally supported (indicated by ,+filter-html-tagsoup, in features), MIME types other than plain text cannot be handled directly ๐Ÿฅบ To deal with specific non-text MIME types or file extensions, programs need to be registered. They either prepare (re-)integrable plain text versions of their input (a mode which is called copiousoutput), or display the content externally, for example in a graphical window. The latter type is only considered by and for the command mimeview.

To install a handler program for a MIME type, an according pipe-TYPE/SUBTYPE variable needs to be set. To define a handler for a file extension, pipe-EXTENSION can be used, and these handlers take precedence ๐Ÿ”ฅ Optionally, mail user agent configuration is supported (see โ€œThe Mailcap filesโ€), and it will be queried for display or quote handlers after the former ones. Type-markers registered via mimetype are the last possible source for information on how to handle a MIME type.

For example, to display HTML messages integrated via the text browsers lynx(1) or elinks(1), register a MathML MIME type and enable its plain text display. To open PDF attachments in an external PDF viewer, asynchronously and with some other magic attached, do this ๐Ÿฆ‹

? if "$features" !% ,+filter-html-tagsoup,
?   #set pipe-text/html='?* elinks -force-html -dump 1'
?   set pipe-text/html='?* lynx -stdin -dump -force_html'
?   # Display HTML as plain text instead
?   #set pipe-text/html=?t
? endif

? mimetype ?t application/mathml+xml mathml

? wysh set pipe-application/pdf='?&=? \
    trap "rm -f \"${MAILX_FILENAME_TEMPORARY}\"" EXIT;\
    trap "trap \"\" INT QUIT TERM; exit 1" INT QUIT TERM;\
    mupdf "${MAILX_FILENAME_TEMPORARY}"'

? define showhtml {
?   \localopts yes
?   \set mime-alternative-favour-rich pipe-text/html=?h?
?   \type "$@"
? }
? \commandalias html \\call showhtml

โœจ Mailing lists โœจ

Known or subscribed-to mailing lists may be flagged in the summary of headers (headline format character %L), and they gain special treatment when sending mails ๐Ÿ“ฌ The variable followup-to-honour will ensure that a Mail-Followup-To: header is honoured when a message is being replied to (reply, followup, Lreply, Lfollowup). followup-to controls creation of this header when creating mails, if the necessary user setup (from, sender) is available. Then, it may also be created automatically, for example when list-replying via Lreply or Lfollowup, or when followup or reply is used and the message's Mail-Followup-To: is honoured, etc. ๐Ÿ’…

The commands mlist and mlsubscribe manage S-nail's notion of which addresses are mailing lists ๐ŸŒˆ With the optional regular expression support, any address which contains any of the magic regular expression characters (^[*+?|$; see re_format(7) or regex(7), dependent on the host system) will be compiled and used as one, possibly matching many addresses. It is not possible to escape the โ€œmagicโ€. In order to match special characters as-is, bracket expressions must be used, for example search @subject@'[[]open bracket'.

? set followup-to followup-to-honour=ask-yes \
    reply-to-honour=ask-yes
? mlist a1@b1.c1 a2@b2.c2 '.*@lists\.c3$'
? mlsubscribe a4@b4.c4 exact@lists.c3

Known and subscribed lists differ in that, for the latter, the user's address is not part of a generated Mail-Followup-To:. There are exceptions, for example if multiple lists are addressed and not all of them have the subscription attribute ๐Ÿค” When replying to a message, its list address (List-Post: header) is automatically and temporarily treated like a known mlist. Dependent on the variable reply-to-honour, an existing Reply-To: is used instead (if it is a single address on the same domain as List-Post:), in order to accept a list administrator's wish that is supposed to have been manifested like that ๐Ÿง

For convenience and compatibility with mail programs that do not honour the non-standard M-F-T, an automatic user entry in the carbon-copy Cc: address list of generated messages can be created by setting followup-to-add-cc. This entry will be added whenever the user will be placed in the Mail-Followup-To: list and is not a regular addressee already. reply-to-swap-in tries to deal with the address rewriting that many mailing lists nowadays perform to work around DKIM / DMARC etc. standard-imposed problems ๐Ÿ’–


โœจ Signed and encrypted messages with S/MIME โœจ

Optionally, S/MIME provides two central mechanisms: message signing and message encryption ๐Ÿ” A signed message contains some data in addition to the regular text. That data can be used to verify that the message has been sent using a valid certificate, that the sender address matches the one in the certificate, and that the message text has not been altered ๐Ÿ•ต๏ธ Signing a message does not change its regular text, so it can be read regardless of whether the recipient's software is able to handle S/MIME. It is thus usually possible to sign all outgoing messages if so desired โœจ

Okay bestie, encryption is basically invisible ink ๐Ÿซฃ๐Ÿ”. It hides the message text from literally everyone except whoever holds the secret decryption key ๐Ÿ—๏ธ. To encrypt something for someone, you need that specific recipient's public encryption key first ๐Ÿ”‘. So you cannot send encrypted mail to people unless you already have their key, either from earlier chats or from public key directories ๐Ÿ“‡. And since signing uses private keys while encryption uses public keys, you should always sign before you encrypt ๐Ÿ’…โœจ.

A central concept in S/MIME is the certification authority, or CA ๐Ÿ‘‘. A CA is a trusted institution that issues certificates. Every certificate can be checked to really come from its CA, as long as you already know the CA's own certificate ๐Ÿ•ต๏ธโ€โ™€๏ธ. A set of CA certificates usually comes bundled and installed with the cryptographic library on your local system ๐Ÿ“ฆ, so reasonable S/MIME security on the Internet depends on whether the source of that library install is trustworthy ๐Ÿซถ. You can also use your own pool of trusted certificates ๐ŸŒธ. If you want that, set smime-ca-no-defaults to skip the default pool, then point smime-ca-file and/or smime-ca-dir at your trusted pool. Just remember that a certificate is never more secure than the method its CA certificate was retrieved with ๐Ÿ™ˆ.

This trusted pool is what the verify command uses to make sure an S/MIME message can actually be trusted ๐Ÿ’–. If it checks out, the verified sender certificates embedded in signed messages can be saved locally with certsave. After that, S-nail uses them to encrypt future messages to those senders ๐Ÿฆ‹:

? certsave FILENAME
? set smime-encrypt-USER@HOST=FILENAME \
    smime-cipher-USER@HOST=AES256

To sign outgoing messages, so receivers can verify where they came from โœ๏ธ, you need a personal S/MIME certificate. S-nail supports password-protected personal certificates and keys too ๐Ÿ”. Check out smime-sign-cert for the details. The section "On URL syntax and credential lookup" covers every place user credentials can come from, and "S/MIME step by step" walks through an example of how to get your own private S/MIME certificate ๐Ÿงš. Generally, if you already have a private key plus certificate "pair", all you need to do is set a few variables:

? set smime-sign-cert=ME@exam.ple.paired \
    smime-sign-digest=SHA512 \
    smime-sign from=myname@my.host

Here are the variables that matter for S/MIME โœจ:

  • General S/MIME: smime-ca-dir, smime-ca-file, smime-ca-flags, smime-ca-no-defaults, smime-crl-dir and smime-crl-file.
  • Signing: smime-sign, smime-sign-cert, smime-sign-include-certs and smime-sign-digest.
  • En- and decryption: smime-cipher and smime-encrypt-USER@HOST.
  • Secondary: content-description-smime-message and content-description-smime-signature.

S/MIME is only available if ,+smime, is included in features ๐ŸŽ€.

[v15 behaviour may differ] Heads up ๐Ÿ˜ญ: neither S/MIME signing nor encryption applies to message subjects or other header fields yet. So those may not hold sensitive info for encrypted messages, and they can't be trusted even when the message content has been verified. When you send signed messages, repeat any important header info in the message text ๐Ÿ’œ.

๐Ÿ”— On URL syntax and credential lookup ๐Ÿ”—

URLs (RFC 3986) are everywhere when it comes to protocol-specific resources ๐ŸŒ. Here they show up in a "normalized" flavour. They're meant as a compact, easy-to-read notation, not for data exchange, so they don't follow any real standard ๐Ÿ™ƒ. Optional bits go in brackets [], either because there's another way to define the info or because the part is protocol specific. /path, for example, is used by the [Option]al Maildir folder type and the IMAP protocol, but not POP3 ๐Ÿ“. If USER and PASSWORD go in a URL server specification, they need URL percent encoded (RFC 3986) forms, which you can make with urlcodec ๐Ÿ“:

PROTOCOL://[USER[:PASSWORD]@]server[:port][/path]

Lots of INTERNAL VARIABLES come in multiple versions, which this document calls "variable chains" ๐Ÿงฉ. There's the plain variable, plus variable-HOST and variable-USER@HOST. If a port was specified, HOST really means server:port, not server. And USER is never in URL percent encoded form ๐Ÿ™…. Say the hypothetical smtp://wings%3Aof@a.dove was used, with user and password included, or it was smtp://a.dove from some other source. To look up the chain tls-config-pairs, first tls-config-pairs-wings:of@a.dove is checked, then tls-config-pairs-a.dove, and only then the plain variable ๐Ÿช„.

The logic for collecting an account's credential information goes like this ๐Ÿ•ต๏ธ:

  • User: A user is always required ๐Ÿซถ. If no USER came in the URL, the variables user-HOST and user get looked up. After that, when enforced by the [Option]al variables netrc-lookup-HOST or netrc-lookup, the user's "The .netrc file" gets searched for a HOST specific entry that provides a login name. Only unambiguous entries are used, meaning exactly one possible matching entry for HOST ๐Ÿ“. If there's still no USER, the verified LOGNAME is used, since it's known to be a valid user on the current host ๐Ÿ’.
  • Authentication: Unless otherwise noted, the chain PROTOCOL-auth-USER@HOST, PROTOCOL-auth-HOST, PROTOCOL-auth gets checked, falling back to a protocol-specific default when necessary ๐Ÿ”.
  • Password: If no PASSWORD was given in the URL, then if the USER was found through the [Option]al netrc-lookup, that lookup may have provided the password too ๐Ÿ—๏ธ. Otherwise, the chain password-USER@HOST, password-HOST, password gets looked up. Once the (now complete) [Option]al chain netrc-lookup-USER@HOST, netrc-lookup-HOST, netrc-lookup is checked, and if it's set, the netrc cache is searched for a password only. Multiple user accounts for a single machine can exist, and so can a fallback entry with no user but with a password ๐Ÿง . If there's still no password but the protocol's chosen authentication type needs one, then in interactive mode you'll get prompted on the terminal โŒจ๏ธ.

Note: S/MIME verification works relative to the values in the From: (or Sender:) header field(s) ๐Ÿ’Œ. That means the values of smime-sign, smime-sign-cert, smime-sign-include-certs and smime-sign-digest aren't looked up using the USER and HOST chains above. Instead, they use the corresponding values from the message being worked on. If no address matches, we assume and use the setting of from. In unusual cases, multiple and different USER and HOST combinations may be involved. On the other hand, those unusual cases become possible ๐ŸŒˆ. The usual case is as short as:

set mta=smtp://USER:PASS@HOST smtp-use-starttls \
    smime-sign smime-sign-cert=+smime.pair \
    from=myname@my.host

The section "EXAMPLES" has complete example configurations ๐Ÿ“š.


๐Ÿ”’ Encrypted network communication ๐Ÿ”’

[Option] SSL (Secure Sockets Layer), and its successor TLS (Transport Layer Security), are protocols that help secure communication by setting up a safely initiated, encrypted network connection ๐Ÿซง. Certificates are the central TLS concept. As part of each connection setup, a (set of) certificate(s) gets exchanged, which lets the identity of the network peer be cryptographically verified. If possible, the TLS/SNI (ServerNameIndication) extension is enabled too, so servers get fine-grained control over which certificates are used ๐ŸŽฏ. A locally installed pool of trusted certificates is then inspected, and verification succeeds if it contains a(n in)direct signer of the presented certificate(s) โœ….

The local pool of trusted so-called CA (Certification Authority) certificates is usually delivered with, and used alongside, the TLS library ๐Ÿ“š. To pick a custom pool, point tls-ca-file and/or (with special preparation) tls-ca-dir at the location you want. Setting tls-ca-no-defaults in addition avoids inspecting the default pool too ๐Ÿ™…โ€โ™€๏ธ. And again, a certificate can't be more secure than the method its CA certificate was retrieved with ๐Ÿซ . For inspection or other purposes, you can fetch a server's certificate (as seen when connecting to it) with the tls command. The port can usually be the protocol name too, and tls-verify is taken into account here ๐Ÿ”:

$ s-nail -vX 'tls certchain SERVER-URL[:PORT]; x'

A local CA pool isn't strictly necessary, though ๐Ÿ™Œ, because server certificates can also be verified via their fingerprint. For this, a message digest gets calculated and compared against the variable chain tls-fingerprint, and verification succeeds if the fingerprint matches ๐Ÿซ†. The digest algorithm is configured via the variable chain tls-fingerprint-digest, and again, tls can be used:

$ s-nail -X 'wysh set verbose; tls fingerprint SERVER-URL[:PORT]; x'

Whether encrypted communication is possible, and which configuration steps you need to enable it, depends on the protocol ๐Ÿงฉ. Some protocols, like POP3S, are implicitly encrypted. Others, like POP3, can upgrade a plain text connection if requested. For example, to use the STLS that POP3 offers, (a member of) the variable (chain) pop3-use-starttls needs to be set. With convenience via shortcut ๐Ÿ’–:

shortcut encpop1 pop3s://pop1.exam.ple

shortcut encpop2 pop3://pop2.exam.ple
set pop3-use-starttls-pop2.exam.ple

set mta=smtps://smtp.exam.ple:465
set mta=smtp://smtp.exam.ple smtp-use-starttls

Okay so normally that's all you have to do, bestie, because TLS libraries try to give you safe defaults ๐Ÿ”’โœจ. But there are plenty of knobs if you wanna tweak stuff ๐Ÿ’…. Certificate verification can be fine-tuned via tls-ca-flags, and the TLS config basics live in tls-config-pairs, which is where you control things like protocol versions or cipher lists ๐ŸŽ€. Earlier on, we hinted at how to lock the protocols down to highly secure ones, but as of writing, the protocol and cipher lists may need to be relaxed so you can connect to some servers ๐Ÿ˜ญ. For example, the following lets you connect to a โ€œLionโ€ running OpenSSL 0.9.8za from June 2014 ๐Ÿฆ (check โ€œINTERNAL VARIABLESโ€ for more on variable chains):

wysh set tls-config-pairs-lion@exam.ple='MinProtocol=TLSv1.1,\
    CipherString=TLSv1.2:!aNULL:!eNULL:\
      ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:\
      DHE-RSA-AES256-SHA:@STRENGTH'

When you're building your own cipher list, the OpenSSL program ciphers(1) is the one to look at ๐Ÿงš. The TLS-related variables worth knowing are tls-ca-dir, tls-ca-file, tls-ca-flags, tls-ca-no-defaults, tls-config-file, tls-config-module, tls-config-pairs, tls-crl-dir, tls-crl-file, tls-rand-file and tls-verify. Also see tls-features. TLS is only available if +tls is included in features ๐ŸŒธ.

๐ŸŒ Character sets ๐ŸŒ

[Option] The user's locale is detected by looking at the LC_ALL environment variable ๐Ÿ‘€. The internal variable ttycharset gets set to the detected terminal character set, so it'll show up in the output of commands like set and varshow โœจ. That character set is the one targeted when displaying data, and user input is expected to be in it too ๐Ÿ’–.

When you create messages, their character input data gets classified ๐Ÿ—‚๏ธ. 7-bit clean text and attachments get classified as charset-7bit. 8-bit data will [Option]ally be converted into members of sendcharsets until one of the conversions succeeds, and charset-8bit is the implied default last member of that list ๐Ÿซถ. If no 8-bit character set can represent your input data, the message won't be sent, and its text can optionally be saved in DEAD ๐Ÿ’€. If that's not acceptable (say, in script environments), mime-force-sendout can be set to force non-convertible data out as application/octet-stream classified binary content instead ๐Ÿ”ฅ. That way receivers still get to inspect the message content, for example via mime-counter-evidence ๐Ÿ•ต๏ธโ€โ™€๏ธ.

If the [Option]al character set conversion isn't available (features misses ,+iconv,), then ttycharset is the only supported character set for non 7-bit clean data, and it's simply assumed it can be used to exchange 8-bit messages ๐Ÿ™ˆ.

ttycharset can also be given an explicit value so you can send mail in a completely โ€œfakedโ€ locale environment ๐ŸŽญ. This is useful for generating and sending stuff like 8-bit UTF-8 input data from a pure 7-bit US-ASCII LC_ALL=C environment (there's an example of this in the section โ€œOn sending mail, and non-interactive modeโ€). Heads up though: reading mail won't really work as expected in a faked environment โš ๏ธ. Even though ttycharset might be addressable, any output gets made safely printable (as via vexpr makeprint) according to the actual locale environment, which ttycharset doesn't affect ๐Ÿ™ƒ.

Classifying 7-bit clean data as charset-7bit is a problem if the input character set (ttycharset) is a multibyte character set that's itself 7-bit clean ๐Ÿ˜ฌ. The Japanese character set ISO-2022-JP is one of these. It's 7-bit clean, but it can encode the rich set of Japanese Kanji, Hiragana and Katakana characters ๐Ÿ‡ฏ๐Ÿ‡ต. To tell receivers about this character set, the mail message has to be MIME encoded so ISO-2022-JP can be advertised, otherwise you'd end up with an invalid email message, and nobody wants that ๐Ÿ’€. To do this, set the variable charset-7bit to ISO-2022-JP. (Honestly, a better approach for email these days is UTF-8, which uses 8-bit bytes for non-US-ASCII data ๐Ÿ’œ.)

When you're replying to a message and reply-in-same-charset is set, the character set of the message being replied to is tried first as a target character set ๐ŸŽฏ. (It's still subject to charsetalias filtering though.) Another option is sendcharsets-else-ttycharset, which reflects the user's locale environment automatically. It treats ttycharset as an implied member of an unset sendcharsets ๐ŸŒˆ.

[Option] When reading messages, their text data gets converted into ttycharset as necessary so it can be displayed on your terminal ๐Ÿ‘‘. Unprintable characters and invalid byte sequences are detected and replaced by substitution characters ๐Ÿ”. You can set up character set mappings for source character sets with charsetalias, which is handy for working around faulty or incomplete character set catalogues (you could, for example, add a missing LATIN1 to ISO-8859-1 mapping) ๐Ÿ› ๏ธ. You can also use it to enforce treating one character set as another one (like โ€œinterpret LATIN1 as CP1252โ€) ๐Ÿงฉ. And see charset-unknown-8bit for dealing with another hairy aspect of message interpretation ๐Ÿ˜ตโ€๐Ÿ’ซ.

If you ever see a message saying โ€œcannot convert from a to bโ€, it means either some characters aren't appropriate for the currently selected (terminal) character set, or the system doesn't support the conversion you need ๐Ÿšจ. In the first case, you'll need to set an appropriate LC_CTYPE locale and/or the variable ttycharset. The best results are usually achieved in a UTF-8 locale on a UTF-8 capable terminal, where the full Unicode spectrum of characters is available ๐ŸŒธ. In that setup you can display characters from all over the world, while still using simpler character sets for sending when you want maximum compatibility with older mail clients ๐Ÿฆ‹.

On the other hand, the POSIX standard defines a locale-independent 7-bit โ€œportable character setโ€ that you should use when overall portability is an issue ๐Ÿงƒ. Even more restricted is the subset named โ€œportable filename character setโ€, which consists of A-Z, a-z, 0-9, period ., underscore _ and hyphen-minus - ๐Ÿ’….

๐Ÿ’Œ Message states ๐Ÿ’Œ

S-nail tells messages apart by their state ๐Ÿซง. The current state is reflected in the summary of headers if the attrlist of the configured headline allows it, and โ€œSpecifying messagesโ€ based on state is possible too โœจ. When you operate on the system inbox, or any other โ€œprimary system mailboxโ€, special actions can kick in when you leave the mailbox (this includes implicit leaving via program termination, unless the exit command was used). Things like automatically moving messages to the โ€œsecondary mailboxโ€ MBOX ๐Ÿ“ฆ. This can be irritating if you're used to โ€œmore modernโ€ mail-user-agents, so the provided global s-nail.rc template sets the internal hold and keepsave variables to suppress this behaviour ๐Ÿ™Œ.

  • new โ€” The message hasn't been viewed or moved to any other state yet ๐Ÿฃ. These messages are kept even in the โ€œprimary system mailboxโ€.
  • unread โ€” The message hasn't been viewed or moved to any other state, but it was already there the last time the mailbox got opened ๐Ÿ‘€. These messages are kept even in the โ€œprimary system mailboxโ€.
  • read โ€” The message was processed by one of these commands: ~f, ~m, ~F, ~M, copy, mbox, next, pipe, Print, print, top, Type, type, undelete โœ…. The commands dp and dt will always try to automatically โ€œstepโ€ and type the โ€œnextโ€ logical message, so they can mark multiple messages as read ๐Ÿƒโ€โ™€๏ธ. The delete command will do so if the internal variable autoprint is set. Except when the exit command is used, messages in a โ€œprimary system mailboxโ€ that are in read state when you leave the mailbox get saved in the โ€œsecondary mailboxโ€ MBOX, unless the internal variable hold is set ๐Ÿ“ฅ.
  • deleted โ€” The message was processed by one of these commands: delete, dp, dt ๐Ÿ—‘๏ธ. Only undelete can be used to get to these messages again ๐Ÿ”„.
  • preserved โ€” The message was processed by a preserve command and it'll be kept in its current location ๐Ÿ”’.
  • saved โ€” The message was processed by save or write ๐Ÿ’พ. Unless the exit command is used, messages in a โ€œprimary system mailboxโ€ that are in saved state when you leave the mailbox will be deleted ๐Ÿ’€. They'll be saved in the โ€œsecondary mailboxโ€ MBOX instead when the internal variable keepsave is set ๐Ÿซถ.

On top of these message states, there are flags. These don't have any technical meaning in the mail system, except that they let you address messages in special ways when โ€œSpecifying messagesโ€ ๐ŸŽ€. Flags are saved with messages, so they're persistent, and they're portable between a set of widely used MUAs ๐ŸŒ.

  • answered โ€” Marks messages as having been answered ๐Ÿ’ฌ.
  • draft โ€” Marks messages as being a draft โœ๏ธ.
  • flag โ€” Marks messages which need special attention ๐Ÿšฉ.

๐ŸŽฏ Specifying messages ๐ŸŽฏ

[Only new quoting rules] COMMANDS that take โ€œMessage list argumentsโ€, like search, type, copy and delete, can perform actions on a bunch of messages at once ๐Ÿซง. If you specify invalid messages or use illegal syntax, errors get reported through the โ€œINTERNAL VARIABLESโ€ !, ^ERR and companions, as well as the command exit status ? ๐Ÿšจ.

For example, delete 1 2 deletes messages 1 and 2, whereas delete 1-5 deletes messages 1 through 5 ๐Ÿ’–. In sorted or threaded mode (see the sort command), delete 1-5 deletes the messages located between (and including) messages 1 through 5 in the sorted/threaded order, as shown in the headers summary ๐Ÿงต.

Errors can pop up for a few reasons ๐Ÿ˜ญ. ^ERR-BADMSG shows up when you request an invalid message, and ^ERR-NOMSG if no applicable message can be found. ^ERR-CANCELED is for missing informational data (mostly thread-related). ^ERR-INVAL covers invalid syntax, and ^ERR-IO covers input/output errors. The following special message names exist ๐Ÿ‘‘:

  • . โ€” The current message, aka the so-called โ€œdotโ€ ๐Ÿ”ต.
  • ; โ€” The message that was previously the current message ๐Ÿ•ฐ๏ธ. It needs to be quoted.
  • , โ€” The parent message of the current message. That's the message with the Message-ID given in the In-Reply-To: field, or the last entry of the References: field of the current message ๐Ÿงšโ€โ™€๏ธ.
  • - โ€” ๐Ÿ”™ the previous undeleted message! For the undelete command, it's the previous deleted message instead. In sorted or threaded mode, it's the previous such message in that order ๐Ÿงต
  • + โ€” โญ๏ธ the next undeleted message (or the next deleted message for undelete). In sorted or threaded mode, it's the next such message in that order ๐Ÿ’–
  • ^ โ€” ๐Ÿฅ‡ the first undeleted message (or the first deleted message for undelete). In sorted or threaded mode, it's the first such message in that order ๐ŸŒธ
  • $ โ€” ๐Ÿ”š the last message. In sorted or threaded mode, it's the last such message in that order. Heads up: this one needs to be quoted, bestie! ๐Ÿ™ˆ
  • &x โ€” ๐Ÿงต๐Ÿ’… in threaded sort mode, this selects the message you address with x (where x is any other message specification), plus all messages from the thread that begins at it. Otherwise, it's identical to x. If you leave x out, you get the thread that begins with the current message.
  • * โ€” ๐ŸŒˆ all messages, every single one!
  • ` โ€” ๐Ÿซถ all messages that were included in the "Message list arguments" of the previous command. This one needs to be quoted! Okay, so a convenient way to read all new messages is to select them via from :n (more on that below), then read them in order with the default command, next, by just typing ` over and over. For this to work, showlast must be set. โœจ
  • x-y โ€” ๐Ÿ”ข an inclusive range of message numbers. The endpoints can also be any of .;-+^$.
  • address โ€” ๐Ÿ“ฎ a case-insensitive "any substring matches" search against the From: header. It'll match addresses too, even if showname is set (POSIX says "any address as shown in a header summary shall be matchable in this form"). BUT if the allnet variable is set, only the local part of the address gets evaluated, the comparison is not case-insensitive, and showname is completely ignored ๐Ÿ™…โ€โ™€๏ธ. For finer control and match boundaries, use the @ search expression.
  • /string โ€” ๐Ÿ” all messages that contain string in the subject field (case ignored according to locale). See also the searchheaders variable. If string is empty, the string from the previous specification of that type gets reused, so no stress!
  • [@name-list]@expr โ€” ๐Ÿ•ต๏ธ all messages that contain the given case-insensitive search expression. If [Option]al regular expression support is available, expr will be interpreted as an (extended) regex if any of the "magic regular expression characters" shows up. If the optional @name-list part is missing, the search is restricted to the subject field body. Otherwise, name-list is a comma-separated list of header fields to search, for example:
'@to,from,cc@Someone i ought to know'

In order to search for a string that includes a @ (commercial at) character, the name-list is effectively non-optional, but it can be given as the empty string. Also, specifying an empty search expression will effectively test for the existence of the given header fields. Some special header fields can be abbreviated: f, t, c, b and s will match From, To, Cc, Bcc and Subject, respectively and case-insensitively. [Option]ally, and just like expr, name-list will be interpreted as an (extended) regular expression if any of the "magic regular expression characters" is seen.

The special names header or < can be used to search in (all of) the header(s) of the message. The special names body or > and text or = will perform full text searches. The former searches only the body, while the latter also searches the message header ([v15 behaviour may differ] this mode yet brute force searches over the entire decoded content of messages, including administrativa strings). ๐Ÿ˜ณ

This specification performs a full text comparison, but even with regular expression support it's almost impossible to write a search expression that safely matches only a specific address domain. To request that the body content of the header is treated as a list of addresses, and to strip those down to the plain email address which the search expression is to be matched against, prefix the effective name-list with a tilde ~:

'@~f,c@@a\.safe\.domain\.match$'
  • :c โ€” ๐Ÿ’œ all messages of a state, or with a matching condition c, where c is one or more of the following colon modifiers:
    • a โ€” answered messages (cf. the variable markanswered) ๐Ÿ—จ๏ธ
    • d โ€” "deleted" messages (for the undelete and from commands only) ๐Ÿ—‘๏ธ
    • f โ€” flagged messages ๐Ÿšฉ
    • L โ€” messages with receivers that match mlsubscribed addresses ๐Ÿ“ฌ
    • l โ€” messages with receivers that match mlisted addresses ๐Ÿ“‹
    • n โ€” "new" messages ๐Ÿ†•
    • o โ€” old messages (any not in state "read" or "new") ๐Ÿง“
    • r โ€” "read" messages ๐Ÿ“–
    • S โ€” [Option] messages with unsure spam classification (see "Handling spam") ๐Ÿค”
    • s โ€” [Option] messages classified as spam ๐Ÿšซ
    • t โ€” messages marked as draft โœ๏ธ
    • u โ€” "unread" messages ๐Ÿ‘€

[Option] IMAP-style SEARCH expressions may also be used! These consist of keywords and criterions. Because "Message list arguments" are split into tokens according to "Shell-style argument quoting," it's necessary to quote the entire IMAP search expression so it stays a single token. This addressing mode is available with all types of mailbox folders, and S-nail will perform the search locally as necessary. โœจ Strings must be enclosed by double quotation marks " in their entirety if they contain whitespace or parentheses. Within the quotes, only reverse solidus \ is recognized as an escape character. All string searches are case-insensitive. When the description says the "envelope" representation of an address field is used, that means the search string is checked against both a list constructed as

'("name" "source" "local-part" "domain-part")'

for each address, and the addresses without real names from the respective header field. These search expressions can be nested using parentheses. See below for examples!

  • (criterion) โ€” all messages that satisfy the given criterion.
  • (criterion1 criterion2 ... criterionN) โ€” all messages that satisfy all of the given criteria.
  • (or criterion1 criterion2) โ€” all messages that satisfy either criterion1 or criterion2, or both. To connect more than two criteria using or specifications, they have to be nested using additional parentheses, as with (or a (or b c)), since (or a b c) really means ((a or b) and c). For a simple or operation of independent criteria on the lowest nesting level, you can achieve similar effects by using three separate criteria, as with (a) (b) (c).
  • (not criterion) โ€” all messages that do not satisfy criterion.
  • (bcc "string") โ€” all messages that contain string in the envelope representation of the Bcc: field.
  • (cc "string") โ€” all messages that contain string in the envelope representation of the Cc: field.
  • (from "string") โ€” all messages that contain string in the envelope representation of the From: field.
  • (subject "string") โ€” all messages that contain string in the Subject: field.
  • (to "string") โ€” all messages that contain string in the envelope representation of the To: field.
  • (header name "string") โ€” all messages that contain string in the specified Name: field.
  • (body "string") โ€” all messages that contain string in their body.
  • (text "string") โ€” all messages that contain string in their header or body.
  • (larger size) โ€” all messages that are larger than size (in bytes).
  • (smaller size) โ€” all messages that are smaller than size (in bytes).
  • (before date) โ€” all messages that were received before date, which must be in the form d[d]-mon-yyyy. Here d is the day of the month as one or two digits, mon is the name of the month (one of Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec), and yyyy is the year as four digits, for example 28-Dec-2012. ๐Ÿ“…
  • (on date) โ€” all messages that were received on the specified date.
  • (since date) โ€” all messages that were received since the specified date.
  • (sentbefore date) โ€” all messages that were sent on the specified date.
  • (senton date) โ€” all messages that were sent on the specified date.
  • (sentsince date) โ€” all messages that were sent since the specified date.
  • () โ€” the same criterion as for the previous search. This specification cannot be used as part of another criterion. If the previous command line contained more than one independent criterion, the last of those criteria is used. ๐Ÿ”

โœจ On terminal control and line editor โœจ

[Option] Terminal control through one of the standard Unix libraries may be available: the Termcap Access Library (libtermcap, -ltermcap) or the Terminal Information Library (libterminfo, -lterminfo). ๐Ÿ–ฅ๏ธ For the TERMinal defined in the environment, interactive usage aspects, for example "Coloured display," and insight of cursor and function keys for the Mailx-Line-Editor (MLE), will be enhanced or enabled. Library interaction can be disabled on a per-invocation basis via termcap-disable, whereas the internal variable termcap is always used as a preferred source of terminal capabilities. ๐ŸŽ€ (For a usage example, see the โ€œFAQโ€ entry โ€œNot "defunctional", but the editor key does not workโ€.)

Okay so the built-in Mailx-Line-Editor (MLE) is [Option] and should work in every environment that follows the ISO C standard ISO/IEC 9899/AMD1:1995 (โ€œISO C90, Amendment 1โ€) ๐Ÿ’…. It'll even support wide glyphs if possible! That functionality was dropped from ISO C, but it's still in the X/Open Portability Guide Issue 4 (โ€œXPG4โ€) โœจ. If you ever want to stop anyone from using a line editor in interactive mode, set line-editor-disable ๐Ÿ™ˆ. Especially if the [Option]al terminal control support is missing, setting entries in termcap will help if the MLE starts misbehaving, so check there for more ๐Ÿ› ๏ธ. The MLE can also support a little bit of colour ๐ŸŒˆ.

[Option] If the history feature is available, input from line editor prompts gets saved in a history list ๐Ÿ“œ that you can search in and expand from ๐Ÿ”. Want something not saved? Prefix it with any amount of whitespace and it's gone from history ๐Ÿ™ˆ. Aspects of history, like allowed content, maximum size, and whether history gets saved persistently, are all configurable with the internal variables history-file, history-gabby, history-gabby-persist and history-size ๐ŸŽ€. There's also the macro hook on-history-addition, which gives you finer control over what actually enters history ๐Ÿงš.

The MLE supports a whole set of editing and control commands ๐Ÿ’–. By default, as many of these as possible get assigned to single-letter control codes, which should work on any terminal. You make one by holding the โ€œcontrolโ€ key while pressing the key you want, for example โ€˜control-Dโ€™ โŒจ๏ธ. If the [Option]al bind command is available, the MLE commands can also be accessed freely. You assign the command name (shown in parentheses in the list below) to any key-sequence you like, and the MLE will instead, and also, use bind to set up its built-in key bindings (more of them if the [Option]al terminal control is available). That whole default setup can be suppressed completely by setting line-editor-no-defaults ๐Ÿซถ. โ€œShell-style argument quotingโ€ notation is used in the list below ๐Ÿ‘‘:

  • \cA โ€” Go to the start of the line (mle-go-home) ๐Ÿ .
  • \cB โ€” Move the cursor backward one character (mle-go-bwd) โฌ…๏ธ.
  • \cC โ€” raise(3) SIGINT (mle-raise-int) ๐Ÿ’ฅ.
  • \cD โ€” Forward delete the character under the cursor. It quits S-nail if used on the empty line, unless the internal variable ignoreeof is set (mle-del-fwd) ๐Ÿ˜ญ.
  • \cE โ€” Go to the end of the line (mle-go-end) ๐Ÿ.
  • \cF โ€” Move the cursor forward one character (mle-go-fwd) โžก๏ธ.
  • \cG โ€” Cancel the current operation with a full reset ๐Ÿงน. If there's an active history search or tabulator expansion, this command resets that first and reverts to the former line content, so you'll need a second reset for a full reset in that case (mle-reset).
  • \cH โ€” Backspace: backward delete one character (mle-del-bwd) ๐Ÿ”™.
  • \cI โ€” [Only new quoting rules] Horizontal tabulator. It tries to expand the word before the cursor, supporting the usual โ€œFilename transformationsโ€ โœจ. This one is affected by mle-quote-rndtrip and line-editor-cpl-word-breaks (mle-complete).
  • \cJ โ€” Newline: commit the current line (mle-commit) โœ….
  • \cK โ€” Cut all characters from the cursor to the end of the line (mle-snarf-end) โœ‚๏ธ.
  • \cL โ€” Repaint the line (mle-repaint) ๐ŸŽจ.
  • \cN โ€” [Option] Go to the next history entry (mle-hist-fwd) ๐Ÿ“œ.
  • \cO โ€” ([Option]ally context-dependent) Invokes the command dt ๐Ÿคทโ€โ™€๏ธ.
  • \cP โ€” [Option] Go to the previous history entry (mle-hist-bwd) ๐Ÿ“œ.
  • \cQ โ€” Toggle roundtrip mode shell quotes, where produced, on and off (mle-quote-rndtrip). This setting is temporary and gets forgotten once the command line is committed, so also see shcodec ๐Ÿ”.
  • \cR โ€” [Option] Complete the current line from the remaining older history entries (mle-hist-srch-bwd) ๐Ÿ”Ž.
  • \cS โ€” [Option] Complete the current line from the remaining newer history entries (mle-hist-srch-fwd) ๐Ÿ”.
  • \cT โ€” Paste the snarf buffer (mle-paste) ๐Ÿ“‹.
  • \cU โ€” The same as \cA followed by \cK (mle-snarf-line) ๐Ÿ’ซ.
  • \cV โ€” Prompts for a Unicode character to be inserted. You type it as a hexadecimal number without prefix, see vexpr (mle-prompt-char) ๐Ÿฆ‹. Heads up: this command needs to be assigned to a single-letter control code in order to be recognized and executed during input of a key-sequence. Only three single-letter control codes can be used for that shortcut purpose. This control code is then special-treated and thus cannot be part of any other sequence, because it triggers mle-prompt-char immediately ๐Ÿ˜ฑ.
  • \cW โ€” Cut the characters from the one preceding the cursor back to the preceding word boundary (mle-snarf-word-bwd) ๐Ÿ—‘๏ธ.
  • \cX โ€” Move the cursor forward one word boundary (mle-go-word-fwd) โฉ.
  • \cY โ€” Move the cursor backward one word boundary (mle-go-word-bwd) โช.
  • \cZ โ€” raise(3) SIGTSTP (mle-raise-tstp) ๐Ÿ˜ด.
  • \c[ โ€” Escape: resets a possibly used multibyte character input state machine and [Option]ally a lingering, incomplete key binding (mle-cancel) ๐Ÿšช. This one also needs to be assigned to a single-letter control code to be recognized and executed during input of a key-sequence, since only three single-letter control codes can be used for that shortcut purpose. This control code may also be part of a multi-byte sequence. But if a sequence is active and this very control code is currently also an expected input, the active sequence takes precedence and consumes the control code ๐Ÿค“.
  • \c\ โ€” ([Option]ally context-dependent) Invokes the command z+ โž•.
  • \c] โ€” ([Option]ally context-dependent) Invokes the command z$ ๐Ÿ’ฒ.
  • \c^ โ€” ([Option]ally context-dependent) Invokes the command z0 0๏ธโƒฃ.
  • \c_ โ€” Cut the characters from the one after the cursor to the succeeding word boundary (mle-snarf-word-fwd) โœ‚๏ธ.
  • \c? โ€” Backspace: mle-del-bwd ๐Ÿ”™.

A few more commands don't get a control code in the list above, but they're still in the mix ๐Ÿ’œ:

  • mle-bell โ€” Ring the audible bell ๐Ÿ””.
  • mle-clear-screen [Option] โ€” Move the cursor home and clear the screen ๐Ÿงน.
  • mle-fullreset โ€” Different to mle-reset, this one immediately resets a possibly active search etc. ๐Ÿ’ฅ.
  • mle-go-screen-bwd โ€” Move the cursor backward one screen width โฌ…๏ธ.
  • mle-go-screen-fwd โ€” Move the cursor forward one screen width โžก๏ธ.
  • mle-raise-quit โ€” raise(3) SIGQUIT ๐Ÿšจ.

Coloured display ๐ŸŒˆ

[Option] Colours and font attributes through ANSI a.k.a. ISO 6429 SGR (select graphic rendition) escape sequences are optionally supported ๐ŸŽจ. Whether they actually get used depends solely on the capability of the detected terminal type (TERM), as fine-tuned through termcap ๐Ÿ’…. You manage colours and font attributes with the multiplexer command colour, and uncolour removes the given mappings. Setting colour-disable suppresses colour and font attribute sequences, but leaves established mappings unchanged ๐Ÿ™ˆ.

Whether colour and font attribute sequences should also be generated when output gets paged through the external PAGER (also see crt) depends on the setting of colour-pager. That's because pagers usually need to be configured before they support ISO escape sequences ๐Ÿ™„. Knowledge of some widely used pagers is built in, though, and in a clean environment it's often enough to just set colour-pager. Check that variable for more on this topic ๐Ÿซถ.

It might make sense to conditionalize your colour setup on interactive mode via if (โ€˜terminalโ€™ indeed means โ€œinteractiveโ€) ๐Ÿง :

if terminal && "$features" =% ,+colour,
  colour iso view-msginfo ft=bold,fg=green
  colour iso view-header ft=bold,fg=red (from|subject) # regex
  colour iso view-header fg=red

  uncolour iso view-header from,subject
  colour iso view-header ft=bold,fg=magenta,bg=cyan
  colour 256 view-header ft=bold,fg=208,bg=230 "subject,from"
  colour mono view-header ft=bold
  colour mono view-header ft=bold,ft=reverse subject,from
endif

Handling spam ๐Ÿ›ก๏ธ

[Option] S-nail can make use of several spam interfaces to identify spam and, in general, deal with spam messages ๐Ÿ•ต๏ธโ€โ™€๏ธ. A precondition for most commands to function is that the spam-interface variable is set to one of the supported interfaces ๐Ÿ’…. โ€œSpecifying messagesโ€ that have been identified as spam is possible via their (volatile) is-spam state, using the :s and :S specifications. Their attrlist entries get used when displaying the headline in the summary of headers ๐Ÿ“‹.

  • spamrate rates the given messages and sets their is-spam flag accordingly ๐Ÿ“Š. If the spam interface offers spam scores, these can be shown in the headline by using the format %$.
  • spamham, spamspam and spamforget interact with the Bayesian filter of the chosen interface and learn the given messages as โ€œhamโ€ or โ€œspamโ€, respectively ๐Ÿง . The last command can be used to cause โ€œunlearningโ€ of messages. It adheres to their current is-spam state and thus reverts previous teachings ๐Ÿ”„.
  • spamclear and spamset will simply set and clear, respectively, the mentioned volatile is-spam message flag, without any interface interaction ๐Ÿช„.

The spamassassin(1) based spam-interface spamc requires a running instance of the spamd(1) server in order to function ๐Ÿ–ฅ๏ธ. You start it with the option --allow-tell if you want Bayesian filter learning to be possible ๐ŸŽ€:

$ spamd -i localhost:2142 -i /tmp/.spamsock -d [-L] [-l]
$ spamd --listen=localhost:2142 --listen=/tmp/.spamsock \
    --daemonize [--local] [--allow-tell]

After that, S-nail can make use of these interfaces โœจ:

$ s-nail -Sspam-interface=spamc -Sspam-maxsize=500000 \
    -Sspamc-command=/usr/local/bin/spamc \
    -Sspamc-arguments="-U /tmp/.spamsock" -Sspamc-user=
or
$ s-nail -Sspam-interface=spamc -Sspam-maxsize=500000 \
    -Sspamc-command=/usr/local/bin/spamc \
    -Sspamc-arguments="-d localhost -p 2142" -Sspamc-user=

Using the generic filter approach lets you use programs like bogofilter(1) ๐Ÿฆ‹. Here's an example, and it needs to be accessible via PATH ๐Ÿ‘‘:

$ s-nail -Sspam-interface=filter -Sspam-maxsize=500000 \
    -Sspamfilter-ham="bogofilter -n" \
    -Sspamfilter-noham="bogofilter -N" \
    -Sspamfilter-nospam="bogofilter -S" \
    -Sspamfilter-rate="bogofilter -TTu 2>/dev/null" \
    -Sspamfilter-spam="bogofilter -s" \
    -Sspamfilter-rate-scanscore="1;^(.+)$"

okay bestie, so the local spam check should go last ๐Ÿ’… because your messages have to actually be sitting in local storage before they can get scored (or used to train the Bayesian filter) ๐Ÿง  ngl that's just the vibe. You can also have spam checked automatically whenever you open certain folders ๐Ÿฆ‹ by setting a special flavor of the internal variable folder-hook, like this:

define spamdelhook {
  # Server side DCC
  spamset (header x-dcc-brand-metrics "bulk")
  # Server-side spamassassin(1)
  spamset (header x-spam-flag "YES")
  del :s # TODO we HAVE to be able to do `spamrate :u ! :sS'
  move :S +maybe-spam
  spamrate :u
  del :s
  move :S +maybe-spam
}
set folder-hook-SOMEFOLDER=spamdelhook

If you want more spam tea โ˜• check out the docs for spam-interface, spam-maxsize, spamc-command, spamc-arguments, spamc-user, spamfilter-ham, spamfilter-noham, spamfilter-nospam, spamfilter-rate and spamfilter-rate-scanscore. They're literally all in there, iykyk ๐Ÿ™ˆ


โœจ COMMANDS โœจ

okay so S-nail reads input line by line ๐ŸŒธ If you end a command line with an unquoted reverse solidus \, it "escapes" the newline. That newline gets tossed, and the next line becomes a follow-up line with all its leading whitespace stripped. Once the whole thing is complete, the whitespace characters space, tabulator and newline (plus anything defined by the variable ifs) get trimmed off the beginning and end ๐Ÿ’– Also, if you put any whitespace at the very start of a line, that command line won't get added to the [Option]al history. Sneaky, but make it make sense ๐Ÿซถ

Next up, the start of the line gets scanned for a known command name. Command names can be abbreviated, and the first command matching your prefix is the one that gets used ๐Ÿงš You can put command modifiers in front of a command to change how it behaves. A name can also be a commandalias, which keeps expanding until there's nothing left to expand ๐Ÿ”ฅ Once S-nail knows what command it's running, the rest of the line gets interpreted using that command's own rules, which we'll get into below.

This is lowkey different from the sh(1)ell ๐Ÿ’€ The sh(1)ell is a whole programming language with syntax that has clearly defined semantics, so it can expand and evaluate each piece of a line one by one. S-nail doesn't do that. For example, ? set one=value two=$one will never assign value to one, because the assignment only happens inside the command (set), long after the expansion already took place ๐Ÿ˜ญ

A list of every command in lookup order is dumped by the command list. You can also use the command help (or ?) with an argument [Option]ally to show a documentation string for the command matching your expanded argument. So ?t works as a shorthand for ?type โœจ Both commands support a more verbose listing mode with these doc strings, which includes the argument type and other info that applies. A handy suggestion would be something like this:

? define __xv {
  # Before v15: need to enable sh(1)ell-style on _entire_ line!
  localopts yes;wysh set verbose;ignerr eval "${@}";return ${?}
}
? commandalias xv '\call __xv'
? xv help set

Command modifiers

Commands can be prefixed by none, or multiple, command modifiers ๐ŸŽ€ Some modifiers only work with a restricted set of commands, and the verbose version of list will ([Option])ally show you which modifiers apply to what.

  • \ โ€” This one has to go first ๐Ÿ’… It stops commandalias expansions on the rest of the line. So \echo will always run the command echo, even if an alias with that same name exists. The content of a commandalias can have its own command modifiers too, including a leading \ to stop further expansions.
  • ignerr โ€” This tells the state machine to ignore any error that the following command throws, so it doesn't cause a program exit even with errexit enabled, or with the standardized exit cases in posix mode ๐Ÿ™ˆ The variable ?, one of the "INTERNAL VARIABLES", gets set to the real exit status of the command no matter what.
  • local โ€” This makes the called command apply its changes only temporarily, local to the block scope ๐Ÿฆ‹ It can only be used inside a defined macro or an account definition. Using it implies the modifier wysh. Local variables don't get inherited by macros deeper in the call chain, and all local settings get garbage collected once you leave the scope. If you want to record and unroll changes in the global scope instead, use the command localopts.
  • scope โ€” does yet not implement any functionality ๐Ÿ’€
  • u โ€” does yet not implement any functionality ๐Ÿ’€
  • vput โ€” Some commands support this one ๐ŸŒˆ When it's used, the command expects the name of a variable as its first argument, and that name can itself be a variable (shell expansion gets applied). The result gets placed in that variable instead of the default spot, which is usually standard output.
    • The given name gets tested to make sure it's a valid sh(1) variable name. That means it can only have upper- and lowercase characters, digits, and the underscore. The hyphen-minus can be used as a non-portable extension. Digits can't be the first character, and hyphen-minus can't be the last ๐Ÿ˜ญ
    • The name must either not be one of the known "INTERNAL VARIABLES", or it has to refer to a writable (non-boolean) value variable.
    • The actual put operation can still fail anyway. For example, if the variable expects a number argument, only a number will be accepted ๐Ÿ™…โ€โ™€๏ธ
    • Any error during these operations makes the command fail, and the error number ! gets set to ^ERR-NOTSUP. The exit status ? should be set to -1, but some commands deviate from that, and any deviation is documented.
  • wysh โ€” Last but not least ๐Ÿ’– This modifier lets some old and established commands use the new "Shell-style argument quoting" rules instead of the traditional "Old-style argument quoting" rules. It's implied if v15-compat is set to a non-empty value.

Old-style argument quoting

[v15 behaviour may differ] This section covers the traditional, POSIX standardized way of quoting non-message list arguments for commands that expect this kind of argument ๐Ÿ“ Most commands still use it, but the new "Shell-style argument quoting" can be used even for those through wysh, one of the "Command modifiers". Still, be careful, because only the new commands were designed with all the capabilities of the new quoting rules in mind. Those rules can, for example, generate control characters, so be warned ๐Ÿ‘€

  • An argument can be wrapped in paired double-quotes "argument" or single-quotes 'argument'. Any whitespace, shell word expansion, or reverse solidus characters inside the quotes get treated literally as part of the argument (except as described in the next bullet). A double-quote is treated literally inside single-quotes, and vice versa. If you need the quote character you're actually using inside the string, escape it with a reverse solidus \, like "y\"ou" โœจ
  • An argument that isn't in quotes can still have spaces, as long as they're reverse solidus escaped, like you\ are ๐ŸŒธ
  • A reverse solidus outside of the enclosing quotes gets discarded, and the character after it is treated literally as part of the argument ๐Ÿซถ

Shell-style argument quoting

The sh(1)ell-style argument parsing and quoting rules, which are POSIX standardized, are used by most commands. [v15 behaviour may differ] Most new commands only support these new rules and are flagged [Only new quoting rules]. Some elder commands can use them with the command modifier wysh. In the future, only this type of argument quoting will remain ๐Ÿ”ฎ

A command line gets parsed from left to right, and an input token is completed whenever an unquoted, otherwise ignored, metacharacter shows up ๐Ÿงฉ The metacharacters are the vertical bar |, ampersand &, semicolon ;, all the characters from the variable ifs, and/or space, tabulator, newline. The extra metacharacters that sh(1) supports, left and right parenthesis (, ) and less-than and greater-than signs <, >, are not used here. They're treated as ordinary characters, because they're a vivid part of email addresses, and it seems highly unlikely that they'll ever become meaningful to S-nail ๐Ÿ’œ

Compatibility note: [v15 behaviour may differ] Please note that even many new-style commands don't yet honour ifs to parse their arguments ๐Ÿ™ƒ The sh(1)ell is a language with syntactic elements that have clearly defined semantics. S-nail, on the other hand, parses entire input lines and decides on a per-command basis what to do with the rest of the line. This also means that whenever an unknown command is seen, all S-nail can do is cancel processing of the remains of the line. It also often depends on an actual subcommand of a multiplexer command how the rest of the line should be treated, and until v15 we aren't capable of this deep inspection of arguments. Nonetheless, at least the following commands, which work with positional parameters, fully support ifs for an almost shell-compatible field splitting: call, call_if, read, vpospar, xcall ๐Ÿ’…

Any unquoted number sign # at the beginning of a new token starts a comment that runs to the end of the line, which also ends argument processing ๐ŸŒ™ An unquoted dollar sign $ causes variable expansion of the given name. That name must be a valid sh(1)ell-style variable name (see vput). "INTERNAL VARIABLES" as well as "ENVIRONMENT" (shell) variables can be accessed this way, and brace enclosing the name is supported, i.e., to subdivide a token ๐Ÿฆ‹

Okay so the metacharacters space, tabulator and newline only finish off an input token ๐Ÿงƒ, but the vertical bar |, ampersand & and semicolon ; are the real main characters ๐Ÿ‘‘. They act as control operators and actually perform control functions ๐Ÿ’…. For now only the semicolon ; is supported โœจ. It terminates a single command, which sequences the whole command line and makes the rest of the line get reevaluated ๐Ÿ”. That means multiple command argument types and quoting rules can all apply to one single line, and before v15 that could get super problematic ๐Ÿ˜ญ. Like, the first of these is going to give you surprising results ๐Ÿ’€:

? echo one; set verbose; echo verbose=$verbose.
? echo one; wysh set verbose; echo verbose=$verbose.

Quoting is the mechanism that strips the special meaning from metacharacters and reserved words, and it stops expansion from happening ๐Ÿซถ. There are four quoting mechanisms: the escape character, single-quotes, double-quotes and dollar-single-quotes:

  • The literal value of any character can be kept by putting the escape character \ (reverse solidus) right in front of it.
  • Arguments wrapped in 'single-quotes' keep their literal value. A single-quote cannot occur within single-quotes, so no nesting drama here ๐Ÿ™…โ€โ™€๏ธ.
  • The literal value of all characters inside "double-quotes" is kept, with a few exceptions ๐ŸŽ€:
    • The dollar sign $ still does variable expansion, as above.
    • The backquote (grave accent) ` doesn't mean anything special yet.
    • The reverse solidus \ escapes any of these: $ (to prevent variable expansion), ` (backquote), " (to prevent ending the quote) and \ (to prevent escaping, i.e., to embed a reverse solidus character as-is). Otherwise it has no special meaning.
  • Arguments enclosed in $'dollar-single-quotes' act like normal single quotes, except that reverse solidus escape sequences get expanded as follows ๐ŸŒˆ:
    • \a โ€” bell control character (ASCII and ISO-10646 BEL).
    • \b โ€” backspace control character (ASCII and ISO-10646 BS).
    • \E โ€” escape control character (ASCII and ISO-10646 ESC).
    • \e โ€” the same.
    • \f โ€” form feed control character (ASCII and ISO-10646 FF).
    • \n โ€” line feed control character (ASCII and ISO-10646 LF).
    • \r โ€” carriage return control character (ASCII and ISO-10646 CR).
    • \t โ€” horizontal tabulator control character (ASCII and ISO-10646 HT).
    • \v โ€” vertical tabulator control character (ASCII and ISO-10646 VT).
    • \\ โ€” emits a reverse solidus character.
    • \' โ€” single quote.
    • \" โ€” double quote (escaping is optional).
    • \NNN โ€” eight-bit byte with the octal value NNN (one to three octal digits), optionally prefixed by an additional 0. A 0 byte will suppress further output for the quoted argument.
    • \xHH โ€” eight-bit byte with the hexadecimal value HH (one or two hexadecimal characters, no prefix, see vexpr). A 0 byte will suppress further output for the quoted argument.
    • \UHHHHHHHH โ€” the Unicode / ISO-10646 character with the hexadecimal codepoint value HHHHHHHH (one to eight hexadecimal characters) ๐Ÿฆ‹. Note that Unicode defines the maximum codepoint ever to be supported as 0x10FFFF (in planes of 0xFFFF characters each). This escape is only supported in locales that support Unicode (see โ€œCharacter setsโ€). In other cases the sequence will remain unexpanded, unless the given code point is ASCII compatible or (if the [Option]al character set conversion is available) can be represented in the current locale. The character NUL will suppress further output for the quoted argument.
    • \uHHHH โ€” Identical to \UHHHHHHHH except it takes only one to four hexadecimal characters.
    • \cX โ€” Emits the non-printable (ASCII and compatible) C0 control codes 0 (NUL) to 31 (US), and 127 (DEL). Printable representations of ASCII control codes can be made by mapping them to a different, visible part of the ASCII character set. Adding the number 64 does this for the codes 0 to 31, here 7 (BEL): 7 + 64 = 71 = G. The real operation is a bitwise logical XOR with 64 (bit 7 set, see vexpr), which also covers code 127 (DEL), mapped to 63 (question mark): ? vexpr ^ 127 64.

Historically circumflex notation was used a lot to visualize control codes, like ^G, but the reverse solidus notation has been standardized: \cG ๐Ÿงš. Some control codes also have standardized (ISO-10646, ISO C) aliases, as shown above (\a, \n, \t etc). Whenever such an alias exists, it will be used for display purposes. The control code NUL (\c@, a non-standard extension) will suppress further output for the remains of the token (which may extend beyond the current quote), or, depending on the context, the remains of all arguments for the current command ๐Ÿ™ˆ.

  • \$NAME โ€” Non-standard extension: expands the given variable name, as above. Brace enclosing the name is supported.
  • \`{command} โ€” Not yet supported, just to raise awareness. Non-standard extension.
Caveats, bestie ๐Ÿ’€:
? echo 'Quotes '${HOME}' and 'tokens" differ!"# no comment
? echo Quotes ${HOME} and tokens differ! # comment
? echo Don"'"t you worry$'\x21' The sun shines on us. $'\u263A'

๐Ÿ’Œ Message list arguments

Many commands operate on message list specifications, as documented in โ€œSpecifying messagesโ€. The argument input is first split into individual tokens via โ€œShell-style argument quotingโ€, which are then interpreted as the mentioned specifications. If no explicit message list has been specified, many commands will search for and use the next message forward that satisfies the commands' requirements. If there are no messages forward of the current message, the search proceeds backwards. If there are no good messages at all to be found, an error message is shown and the command is aborted ๐Ÿ˜ญ. The verbose output of the command list will indicate whether a command searches for a default message, or not ๐Ÿ”.

๐Ÿงƒ Raw data arguments for codec commands

A special set of commands, which all have the string โ€œcodecโ€ in their name, like addrcodec, shcodec, urlcodec, take raw string data as input. This means the content of the command input line is passed completely unexpanded and otherwise unchanged โœจ. Like this, the effect of the actual codec is visible without any noise from possible shell quoting rules etc., so you can input one-to-one the desired or questionable data ๐Ÿ’–. To gain a level of expansion, the entire command line can be evaluated first, for example:

? vput shcodec res encode /usr/Schรถnes Wetter/heute.txt
? echo $res
$'/usr/Sch\u00F6nes Wetter/heute.txt'
? shcodec d $res
$'/usr/Sch\u00F6nes Wetter/heute.txt'
? eval shcodec d $res
/usr/Schรถnes Wetter/heute.txt

๐Ÿ“ Filename transformations

Filenames, where expected, and unless documented otherwise, are subsequently subject to the following filename transformations, in sequence ๐ŸŽ€:

  • If the given name is a registered shortcut, it will be replaced with the expanded shortcut. This step is mostly taken for folders only.
  • The filename is matched against the following patterns or strings. But for plus +file folder expansion, this step is mostly taken for folders only.
    • # (Number sign) is expanded to the previous file.
    • % (Percent sign) is replaced by the invoking user's primary system mailbox. That's either the (itself expandable) inbox if that is set, the standardized absolute pathname indicated by MAIL if that is set, or a built-in compile-time default otherwise. When opening a folder, the used name is actively checked for being a primary mailbox, first against inbox, then against MAIL ๐Ÿ”Ž.
    • %user expands to the primary system mailbox of user (and never the value of inbox, regardless of its actual setting).
    • & (Ampersand) is replaced with the invoking user's secondary mailbox, the MBOX.
    • +file refers to a file in the folder directory (if that variable is set).
    • %:filespec expands to the same value as filespec, but it has special meaning when used with, for example, the command folder. The file will be treated as a primary system mailbox by, among others, the mbox and save commands, meaning that messages that have been read in the current session will be moved to the MBOX mailbox instead of simply being flagged as read ๐Ÿ’….
  • Meta expansions may be applied to the resulting filename, as allowed by the operation and applicable to the resulting access protocol (also see โ€œOn URL syntax and credential lookupโ€). For the file-protocol, a leading tilde ~ character will be replaced by the expansion of HOME, except when followed by a valid user name. In that case, the home directory of the given user is used instead ๐ŸŒธ.

A shell expansion as if you typed it in double-quotes (see โ€œShell-style argument quotingโ€) can be applied ๐Ÿ’…. That means any $VARIABLE (or ${VARIABLE}) gets swapped out for the variable's value, if it's possible โœจ. โ€œINTERNAL VARIABLESโ€ and โ€œENVIRONMENTโ€ (shell) variables are both reachable through this mechanism ๐ŸŒธ.

Shell pathname wildcard pattern expansions (glob(7)) can also be applied, as documented. If the fully expanded filename turns into multiple pathnames and the command only wants one file, you get an error ๐Ÿ’€.

In interactive context, arguments are usually shown in a properly quoted form so that hitting ENTER just works ๐Ÿซถ. So a file diet\ is \curd.txt might be displayed as 'diet\ is \curd.txt' ๐ŸŽ€.

The following commands are available ๐Ÿฆ‹:

โœจ ! โœจ

This runs the SHELL command that follows ๐Ÿš. Unescaped exclamation marks get replaced with the previously executed command if the internal variable bang is set. This command supports vput as documented in โ€œCommand modifiersโ€, and it manages the error number !. A 0 or positive exit status ? reflects the exit status of the command. Negative ones mean an error happened before the command was executed, or that the program didn't exit cleanly (maybe because of a signal). In that case the error number is ^ERR-CHILD ๐Ÿ˜ญ.

With the vput modifier, a few special cases come into play ๐Ÿงš. A negative exit status means the collected data couldn't be stored in the given variable, which is a ^ERR-NOTSUP error that shouldn't really happen. ^ERR-CANCELED means no temporary file could be created to collect the command output at first glance. If there's a catchable out-of-memory situation, you'll get ^ERR-NOMEM, and S-nail will try to store the empty string, just like with all other detected error conditions ๐Ÿ’œ.

โœจ # โœจ

The comment command makes the entire line get ignored ๐Ÿ™ˆ. Heads up: this is a normal command whose whole purpose is to throw its arguments away. It is not a special โ€œcomment-startโ€ character. So trailing comments on a line aren't possible, except for commands that use โ€œShell-style argument quotingโ€ ๐ŸŒˆ.

โœจ + โœจ

Goes to the next message in sequence and types it, just like ENTER ๐Ÿ’–.

โœจ - โœจ

Displays the preceding message, or the n'th previous message if you give it a numeric argument n ๐Ÿ“.

โœจ = โœจ

Without arguments, this shows the message number of the current message (the โ€œdotโ€). With arguments, it shows the numbers of the given list instead ๐Ÿ”ฅ. The output numbers get separated from each other with the first character of ifs. They're followed by the first character of if-ws, as long as that isn't empty and isn't identical to the first one. If that results in no separation at all, a space character gets used instead. This command supports vput (see โ€œCommand modifiersโ€), and it manages the error number ! ๐Ÿ‘‘.

โœจ ? โœจ

[Option] This shows a brief summary of commands ๐ŸŒธ.

[Option] Give it an argument and you'll see a synopsis for that command instead. Commands can be abbreviated in general, and this command can show the full expansion of an abbreviation, synopsis included. Try ?h, ?hel and ?help and watch how the output changes ๐Ÿ‘€. To stop aliases from being resolved, you can prepend the modifier \ to the argument. Just note that it must be quoted. This mode also supports a more verbose output, which provides the information documented for list ๐Ÿ’….

โœจ | โœจ

A synonym for the pipe command ๐Ÿฆ‹.

โœจ account, unaccount โœจ

(ac, una) Creates, selects or lists (an) account(s) ๐ŸŽ€. Accounts are special incarnations of defined macros and group commands, plus variable settings, which together usually arrange the environment for creating an email account. Different to normal macro settings, which are covered by localopts (here by default enabled!), accounts won't be reverted before the account is changed again ๐Ÿงš. The special account null (case-insensitive) always exists. All but it can be deleted by the latter command, and in one operation with the special name *. Also, for all but it, a possibly set on-account-cleanup hook gets called once they're left, including on program exit โœจ.

Without arguments you get a listing of all defined accounts ๐Ÿ“‹. With one argument, the given account gets activated: its system inbox is activated (as via folder), a possibly installed folder-hook runs, and the internal variable account gets updated ๐Ÿ’–. The two-argument form behaves identically to defining a macro with define ๐Ÿซถ. Important settings for accounts include folder, from, hostname, inbox, mta, password and user (โ€œOn URL syntax and credential lookupโ€). There are also things like tls-config-pairs (โ€œEncrypted network communicationโ€), plus protocol specifics like imap-auth, pop3-auth and smtp-auth ๐ŸŒธ.

account myisp {
  set folder=~/mail inbox=+syste.mbox record=+sent.mbox
  set from='(My Name) myname@myisp.example'
  set mta=smtp://mylogin@smtp.myisp.example
}

โœจ addrcodec โœจ

This performs email address codec transformations on a raw-data argument, rather according to email standards (RFC 5322; [v15 behaviour may differ] will furtherly improve) ๐Ÿ“ฎ. It supports vput (see โ€œCommand modifiersโ€), and it manages the error number !. The first argument must be either [+[+[+]]]e[ncode], d[ecode], s[kin] or skinl[ist]. It specifies the operation to perform on the rest of the line ๐Ÿ’œ.

Decoding shows how a standard-compliant MUA would display the given argument, which should be an email address ๐Ÿ‘€. Heads up, though: most MUAs have difficulties with address standards ๐Ÿ˜ญ. They vary wildly whenever (comments) in parenthesis, โ€œdouble-quotedโ€ strings, or quoted-pairs (as below) get involved. [v15 behaviour may differ] S-nail currently does not perform decoding when displaying addresses ๐Ÿ™ˆ.

Skinning is identical to decoding, but it only outputs the plain address, without any string, comment, etc. components โœจ. Another difference: it may fail with the error number ! set to ^ERR-INVAL if decoding can't find a (valid) email address. In that case, the unmodified input is output again ๐Ÿ’€.

skinlist first performs a skin operation, and then checks whether a valid address is a registered mailing list (see mlist and mlsubscribe). It eventually reports that state in the error number ! as ^ERR-EXIST. (This state could later get overwritten by an I/O error, though ๐Ÿฅฒ.)

Encoding supports four different modes ๐Ÿฆ‹. Lesser automated versions can be chosen by prefixing one, two or three plus signs. The standard gives some characters a special meaning. Those have to be transformed into so-called quoted-pairs by pairing them with a reverse solidus \ in order to remove that special meaning. Be warned: this might change the interpretation of the entire argument from what you wanted! ๐Ÿ”ฅ Specify one plus sign to leave parenthesis alone. Use two to stop double quotation marks from turning into quoted-pairs, and three to also leave any user-specified reverse solidus alone ๐Ÿ’…. The result will always be valid if a successful exit status is reported ([v15 behaviour may differ] the current parser fails this assertion for some constructs) ๐Ÿ™ˆ. [v15 behaviour may differ] Addresses need to be specified between angle brackets < and > if the construct gets more difficult. Otherwise the current parser will fail, because it is not smart enough to guess right ๐Ÿ˜ญ.

? addrc enc "Hey, you",<diet@exam.ple>\ out\ there
"\"Hey, you\", \\ out\\ there" <diet@exam.ple>
? addrc d "\"Hey, you\", \\ out\\ there" <diet@exam.ple>
"Hey, you", \ out\ there <diet@exam.ple>
? addrc s "\"Hey, you\", \\ out\\ there" <diet@exam.ple>
diet@exam.ple

โœจ alias, unalias โœจ

[Only new quoting rules] (a, una) These define or list (and, respectively, remove) address aliases ๐Ÿ“‡. Aliases are a method of creating personal distribution lists, mapping a single name to none, one or multiple receivers. They get expanded after โ€œCompose modeโ€ is left, and the expansion correlates with metoo ๐ŸŒˆ. The latter command removes all given aliases. The special name asterisk * removes all existing aliases ๐Ÿงน. Without arguments, the former shows a list of all currently known aliases. With one argument, it shows only the target(s) of the given one. When given two arguments, with hyphen-minus - being the first, the target(s) of the second get expanded recursively ๐Ÿซถ.

In all other cases the given alias is newly defined, or gets appended to. Arguments must either be valid alias names themselves, or any other address type (see โ€œOn sending mail, and non-interactive modeโ€) ๐Ÿ’–. Recursive expansion of aliases can be prevented by prefixing the desired argument with the modifier reverse solidus \. A valid alias name conforms to mta-aliases syntax, but follow-up characters can also be the number sign #, colon :, commercial at @, exclamation mark !, period ., as well as โ€œany character that has the high bit setโ€ ๐Ÿ“. The dollar sign $ may be the last character. The number sign # may need โ€œShell-style argument quotingโ€ ๐ŸŽ€.

[v15 behaviour may differ] okay bestie, the colon is currently not supported ๐Ÿ˜ญ because it messes with the normal address parsing rules. [v15 behaviour may differ] high bit characters will probably throw warnings rn for the same reasons colon is unsupported ๐Ÿ™ˆ, and later on, locale dependent character set validity checks will be performed ๐ŸŒˆ.

? alias cohorts  bill jkf mark kridle@ucbcory ~/cohorts.mbox
? alias mark  mark@exam.ple
? set mta-aliases=/etc/aliases

alternates, unalternates ๐Ÿฆ‹

[Only new quoting rules] (alt) okay so this one manages a list of alternate addresses or names for the active user ๐Ÿ’…. Anyone on that list gets removed from recipient lists, except for one ๐Ÿซถ. There's a set of implicit alternates that gets built from the values of LOGNAME, from, sender and reply-to ๐Ÿ‘‘. Just know that from won't be used if sender is set. The latter command removes the alternates you give it, and the special name * will nuke every existing alternate name ๐Ÿ’€.

The former command manages the error number !. Used with no arguments, it shows the current set of alternates, and only in this mode does it also support vput (see โ€œCommand modifiersโ€) โœจ. Otherwise, the arguments you give it (after being checked for validity) get appended to the list of alternate names. In posix mode, though, they replace that list instead ๐ŸŒธ.

answered, unanswered ๐Ÿ’Œ

Take a message list and mark each message as (not) having been answered โœ…. Messages get marked answered automatically when you reply to them, but only if the markanswered variable is set. Check out the section โ€œMessage statesโ€ for the full tea ๐Ÿต.

bind, unbind ๐ŸŽ€

[Option][Only new quoting rules] the bind command lets you extend the MLE (see โ€œOn terminal control and line editorโ€) with freely configurable key bindings, and it's giving customization queen ๐Ÿ‘‘. The latter command, unbind, removes the given key binding from the given context. Both arguments can be a wildcard *, so unbind * * will remove all bindings of all contexts ๐Ÿ’ฅ. Heads up though: due to initialization order, unbinding won't work for built-in key bindings at program startup ๐Ÿ˜ญ. For that, use line-editor-no-defaults instead ๐Ÿซถ.

With zero arguments, or with just a context name, the former command shows all key bindings. If you pass a context, it shows the bindings for that context only. An asterisk * will iterate over all contexts ๐Ÿ”. You'll get a more verbose listing if either debug or verbose is set ๐Ÿงš.

With two or more arguments, you can view a specific binding or (re)establish it. The first argument is the context the binding applies to. The second argument is a comma-separated list of the โ€œkeysโ€ that form the binding. Any further arguments get joined to form the expansion, and they create or update the binding ๐Ÿฆ‹. If you want a binding to be editable by the user instead of auto-committed, put a commercial at @ as the last thing in the expansion. That @ gets removed, and leading and trailing whitespace gets trimmed off the expansion too โœ‚๏ธ. Heads up: a reverse solidus can't be the last character of an expansion ๐Ÿ™…โ€โ™€๏ธ, and an empty expansion gets rejected ๐Ÿšซ.

Contexts decide when a binding applies. A binding won't be seen unless its context is currently active ๐Ÿ‘€. The shared binding base is the exception: it's the foundation for all other bindings and always applies, but its bindings only apply secondarily ๐ŸŒˆ. The available contexts are:

  • base โ€” the shared context, the foundation for everything else ๐Ÿงฑ
  • default โ€” used in all situations that aren't otherwise documented ๐Ÿ“„
  • compose โ€” applies only to โ€œCompose modeโ€ โœ๏ธ

Bindings are specified as a comma-separated list of byte sequences, where each list entry is one โ€œkeyโ€ (press) ๐ŸŽน. Byte sequence boundaries get forcefully terminated after bind-inter-byte-timeout milliseconds, while key sequences can be timed out via bind-inter-key-timeout โฑ๏ธ. A list entry can also refer to the name of a terminal capability if it has a leading colon :. Several dozen names are compiled in, and you can use either their terminfo(5) name or, if one exists, their termcap(5) name. This works no matter which [Option]al terminal control library is actually being used ๐Ÿ–ฅ๏ธ. Any capability works too, as long as the name is resolvable by the [Option]al control library, or was defined via the internal variable termcap ๐Ÿ’…. Input sequences aren't case-normalized, so you need an exact match to update or remove a binding ๐Ÿ” . It's a good idea to start user bindings (as opposed to purely terminal capability based ones) with an escape or other control character, like โ€˜\cAโ€™, to avoid ambiguities. It also reduces search time โšก. Here are some examples:

? bind base a,b echo one
? bind base $'\E',d mle-snarf-word-fwd # Esc(ape)
? bind base $'\E',$'\c?' mle-snarf-word-bwd # Esc,Delete
? bind default $'\cA',:khome,w 'echo Editable binding@'
? bind default a,b,c rm -irf / @  # Also editable
? bind default :kf1 File %
? bind compose :kf1 ~v
โš ๏ธ bestie, that rm -irf / one is just an example of syntax. Do NOT go running it in your actual shell ๐Ÿ’€.

Note that the entire comma-separated list is first parsed (over) as a shell-token, with whitespace as the field separator. Then it gets parsed and expanded for real, with comma as the field separator. So whitespace needs to be properly quoted. See โ€œShell-style argument quotingโ€ for that ๐Ÿง. Using Unicode reverse solidus escape sequences makes a binding defunctional if the locale doesn't support Unicode (see โ€œCharacter setsโ€) ๐Ÿฅฒ. Using terminal capabilities does the same if no (corresponding) terminal control support is (currently) available ๐Ÿ’”. Adding, deleting or modifying a key binding invalidates the internal prebuilt lookup tree. It gets recreated as necessary, and this process gets visualized in most verbose as well as in debug mode ๐Ÿ”„.

The following terminal capability names are built-in. They can be used in terminfo(5) or, if available, the two-letter termcap(5) notation. See the respective manual for a full list of capabilities ๐Ÿ“š. The program infocmp(1) can show all the capabilities of TERM or the given terminal type. Use the -x flag to also show supported (non-standard) extensions ๐Ÿ”.

  • kbs or kb โ€” Backspace ๐Ÿ”™
  • kdch1 or kD โ€” Delete character ๐Ÿ—‘๏ธ
  • kDC or *4 โ€” shifted variant
  • kel or kE โ€” Clear to end of line ๐Ÿงน
  • kext or @9 โ€” Exit ๐Ÿšช
  • kich1 or kI โ€” Insert character โž•
  • kIC or #3 โ€” shifted variant
  • khome or kh โ€” Home ๐Ÿ 
  • kHOM or #2 โ€” shifted variant
  • kend or @7 โ€” End ๐Ÿ”š
  • knp or kN โ€” Next page โญ๏ธ
  • kpp or kP โ€” Previous page โฎ๏ธ
  • kcub1 or kl โ€” Left cursor (with more modifiers: see below) โฌ…๏ธ
  • kLFT or #4 โ€” shifted variant
  • kcuf1 or kr โ€” Right cursor (ditto) โžก๏ธ
  • kRIT or %i โ€” shifted variant
  • kcud1 or kd โ€” Down cursor (ditto) โฌ‡๏ธ
  • kDN โ€” shifted variant (only terminfo)
  • kcuu1 or ku โ€” Up cursor (ditto) โฌ†๏ธ
  • kUP โ€” shifted variant (only terminfo)
  • kf0 or k0 โ€” Function key 0. Add one for each function key up to kf9 and k9, respectively ๐Ÿ”ข
  • kf10 or k; โ€” Function key 10 ๐Ÿ”Ÿ
  • kf11 or F1 โ€” Function key 11. Add one for each function key up to kf19 and F9, respectively ๐ŸŽน

Some terminals support key-modifier combination extensions, like โ€˜Alt+Shift+xyโ€™ ๐ŸŒˆ. Take the delete key, kdch1, for example. In its shifted variant, the name mutates to kDC, and then a number gets appended for each state. Alt is kDC3, Shift+Alt is kDC4, Control is kDC5, Shift+Control is kDC6, Alt+Control is kDC7, and finally Shift+Alt+Control is kDC8 ๐Ÿ’–. The left cursor key, kcub1, works the same way: KLFT, KLFT3, KLFT4, KLFT5, KLFT6, KLFT7, KLFT8 โœจ.

call ๐Ÿ“ž

[Only new quoting rules] call calls the given macro. The macro must have been created via define (see there for more), otherwise you'll get an ^ERR-NOENT error ๐Ÿ˜ค. Heads up: calling macros recursively will eventually exceed the stack size limit, which causes a hard program abortion ๐Ÿ’€. If recursively calling a macro is the last command of the current macro, consider using the command xcall instead. It releases all resources of the current macro first, and only then replaces the current macro with the called one ๐Ÿซถ.

call_if ๐Ÿฆ‹

Identical to call if the given macro has been created via define. The difference is that call_if doesn't fail nor warn if the macro doesn't exist ๐Ÿฅฐ.

cd ๐ŸŒธ

Synonym for chdir ๐Ÿ’….

certsave ๐Ÿ”

[Option] Only applicable to S/MIME signed messages ๐Ÿ“ฉ. It takes an optional message list and a filename, then saves the certificates contained within the message signatures to the named file. They get saved in both human-readable and PEM format ๐Ÿ“œ. Later, you can use those certificates to send encrypted messages to the respective message senders, by setting smime-encrypt-USER@HOST variables ๐Ÿ”’.

charsetalias, uncharsetalias ๐ŸŒ

[Only new quoting rules] these two manage alias mappings for (conversion of) โ€œCharacter setsโ€ ๐Ÿ—บ๏ธ. Alias processing isn't performed for โ€œINTERNAL VARIABLESโ€, for example charset-8bit. Mappings are ineffective if character set conversion isn't available, which you can check because features won't announce โ€˜,+iconv,โ€™ ๐Ÿ™…โ€โ™€๏ธ. Expansion happens recursively for cases where aliases point to other aliases, with a built-in loop limit of 8 ๐Ÿ”.

Okay so, unalias is the bouncer ๐Ÿšช: it deletes every alias you hand it, or nukes them all at once if you pass the asterisk * ๐Ÿ’€. alias is the info queen ๐Ÿ‘‘. With no args she lists every alias that's defined, with one arg she shows that alias's target, and when you give two args with hyphen-minus - first, the second one gets expanded recursively ๐Ÿ”. Anything else gets treated as pairs of character sets and their target alias name, which creates new aliases or updates existing ones โœจ.

๐ŸŒธ chdir

[Only new quoting rules] (ch) โ€” Bestie, this one just takes you back to HOME or wherever you point it ๐Ÿก. Change the working directory to HOME or the given argument. Synonym for cd ๐Ÿ’–.

๐Ÿงต collapse, uncollapse

This one only works in threaded sort mode ๐Ÿงต. It takes a message list and hides all the replies to those messages in header summaries ๐Ÿ™ˆ, except for new messages and the โ€œdotโ€ ๐ŸŽฏ. When you display a message that has collapsed replies, all of them get uncollapsed automatically ๐Ÿช„. The latter command, uncollapse, undoes the collapsing ๐Ÿซถ.

๐ŸŽจ colour, uncolour

[Option][Only new quoting rules]

Okay, colour is where you set the vibes for your โ€œColoured displayโ€ ๐ŸŒˆ, and uncolour undoes it ๐Ÿงน. Without arguments, colour shows every mapping that's currently defined โœจ. Otherwise you need a colour type (case-insensitive), and it has to be one of these:

  • 256 โ€” for 256-colour terminals ๐ŸŒˆ
  • 8, ansi or iso โ€” the standard 8-colour ANSI / ISO 6429 colour palette ๐ŸŽจ
  • 1 or mono โ€” for monochrome terminals, which only support (some) font attributes ๐Ÿ–ค

Without further arguments, you get the list of all currently defined mappings of the given type. The special all or * also shows all currently defined mappings, no matter the type ๐Ÿ‘€.

Otherwise, the second argument is the mappable slot ๐ŸŽฏ, the third argument is a (comma-separated list of) colour and font attribute specification(s) ๐Ÿ’…, and the optional fourth argument is a precondition ๐Ÿง . Conditioned mappings get tested in (creation) order until a (case-insensitive) match turns up. The default mapping (if one has been established) is only picked as a last resort ๐Ÿ™ˆ. The preconditions you can use depend on the mappable slot, and these ones exist:

Mappings starting with mle- are for the [Option]al built-in Mailx-Line-Editor (MLE, see โ€œOn terminal control and line editorโ€) ๐Ÿ–Š๏ธ, and they don't support preconditions.

  • mle-position โ€” the position indicator that shows up when a line can't fit fully on the screen ๐Ÿ“
  • mle-prompt โ€” used for the prompt ๐Ÿ’ฌ
  • mle-error โ€” the occasional error indicator that gets joined onto the prompt ๐Ÿšจ [v15 behaviour may differ] Also used for error messages written on standard error.

Mappings starting with sum- are used in header summaries ๐Ÿ“‹. They all understand the preconditions dot (the current message) and older for elder messages (only honoured in conjunction with datefield-markout-older) ๐Ÿ‘ต.

  • sum-dotmark โ€” the โ€œdotmarkโ€ you can make with the %> or %< formats of the variable headline ๐ŸŽฏ
  • sum-header โ€” the complete header summary line, except the โ€œdotmarkโ€ and the thread structure ๐Ÿ“
  • sum-thread โ€” the thread structure you can make with the %i format of the variable headline ๐Ÿงต

Mappings starting with view- are used when you're displaying messages ๐Ÿ‘€.

  • view-from_ โ€” for so-called From_ lines, which are MBOX file format specific header lines (also see mbox-rfc4155) ๐Ÿ“ฆ
  • view-header โ€” for header lines ๐Ÿ“จ. You can give a comma-separated list of headers the mapping applies to as a precondition. If [Option]al regular expression support is available, then once any of the โ€œmagic regular expression charactersโ€ shows up, the precondition gets evaluated as an (extended) one ๐Ÿช„.
  • view-msginfo โ€” for the introductory message info line ๐Ÿ‘‹
  • view-partinfo โ€” for MIME part info lines ๐Ÿงฉ

These (case-insensitive) colour definitions and font attributes are understood, and you can stack multiple of them in a comma-separated list ๐Ÿ’–:

  • ft= โ€” a font attribute: bold, reverse or underline ๐Ÿ’ช. It's possible (and often applicable) to specify multiple font attributes for a single mapping โœจ.
  • fg= โ€” foreground colour attribute, in this order (numbers 0 - 7): black, red, green, brown, blue, magenta, cyan or white ๐ŸŽจ. To use 256-colour mode, you can give a decimal number colour spec in the range 0 to 255, inclusive, which gets interpreted like this:
    • 0 - 7 โ€” the standard ISO 6429 colours, as above ๐ŸŒˆ
    • 8 - 15 โ€” high intensity variants of the standard colours โญ
    • 16 - 231 โ€” 216 colours in tuples of 6 ๐ŸŽ€
    • 232 - 255 โ€” grayscale from black to white in 24 steps ๐Ÿฉถ

Here's the script for that, bestie ๐Ÿ’…:

#!/bin/sh -
fg() { printf "\033[38;5;${1}m($1)"; }
bg() { printf "\033[48;5;${1}m($1)"; }
i=0
while [ $i -lt 256 ]; do fg $i; i=$(($i + 1)); done
printf "\033[0m\n"
i=0
while [ $i -lt 256 ]; do bg $i; i=$(($i + 1)); done
printf "\033[0m\n"
  • bg= โ€” background colour attribute (see fg= for possible values) ๐Ÿ–ค

uncolour removes the given mapping for the given colour type ๐Ÿงน. The special type * selects all types. If the optional precondition argument is given, only the exact tuple of mapping and precondition gets removed. The special name * removes all mappings (no precondition allowed), so uncolour * * removes all established mappings ๐Ÿ’€.


๐Ÿช„ commandalias, uncommandalias

[Only new quoting rules]

commandalias defines or lists command aliases, and uncommandalias removes them ๐Ÿ’ซ. An (command)alias can be used everywhere a normal command can be used, but it always takes precedence ๐Ÿ‘‘. Any arguments you give the command alias get joined onto the alias expansion, and the resulting string forms the command line that's, in effect, executed ๐Ÿƒโ€โ™€๏ธ. uncommandalias removes all the given aliases, and the special name asterisk * removes all existing aliases ๐Ÿงน. Without arguments, commandalias shows a list of all currently known aliases. With one argument, it shows just the expansion of that one ๐Ÿ‘€.

With two or more arguments, you define or update a command alias ๐Ÿ› ๏ธ. The first argument is the name the remaining command line should be accessible under, and the content can be just about anything ๐ŸŽ. An alias can expand to another alias, but to avoid expansion loops, further expansion gets prevented if an alias refers to itself or if an expansion depth limit is reached ๐Ÿ”. Explicit expansion prevention is available via reverse solidus \, one of the โ€œCommand modifiersโ€ ๐Ÿšซ.

? commandalias xx
s-nail: `commandalias': no such alias: xx
? commandalias xx echo hello,
? commandalias xx
commandalias xx 'echo hello,'
? xx
hello,
? xx world
hello, world

๐Ÿ’Œ Copy (C)

Similar to copy ๐Ÿ“จ, but it copies the messages to a file named after the local part of the sender of the first message, instead of taking a filename argument ๐Ÿชช. outfolder gets inspected to decide on the actual storage location ๐Ÿ“‚.

๐Ÿ’พ copy (c)

Copy messages to the named file and do not mark them as being saved ๐Ÿ™…. Otherwise identical to save ๐Ÿ’….

๐Ÿงฎ csop

[Only new quoting rules]

csop is a multiplexer command ๐Ÿงฐ that gives you C-style string operations on 8-bit bytes. It has no notion of locale settings and character sets, so it effectively assumes ASCII data ๐Ÿ”ค. For numeric and other operations, refer to vexpr ๐Ÿ”ข. vput, one of the โ€œCommand modifiersโ€, is supported ๐ŸŽ. The error result is -1 for usage errors and numeric results, and the empty string otherwise โŒ. Missing data errors, like unsuccessful searches, set the ! error number to ^ERR-NODATA ๐Ÿ™ˆ. Where the question mark ? modifier suffix is supported, a case-insensitive (ASCII mapping) operation mode is available ๐Ÿ” . The keyword case is optional, so find? and find?case are identical ๐Ÿ’–.

  • length โ€” queries the length of the given argument ๐Ÿ“
  • hash, hash32 โ€” calculates a hash value of the given argument ๐Ÿ”. The latter returns a 32-bit result regardless of host environment ๐Ÿ’ป. The ? modifier suffix is supported ๐Ÿ’…. These use Chris Torek's hash algorithm, and the resulting hash value is bit mixed as shown by Bret Mulvey ๐Ÿงช.
  • find โ€” searches for the second argument in the first one ๐Ÿ”. If it's found, it shows the resulting 0-based offset ๐Ÿ“. The ? modifier suffix is supported ๐Ÿ’….

๐ŸŽ€ substring

okay so substring makes a substring out of its first argument ๐Ÿงš. The optional second argument is the 0-based starting offset, and a negative one counts from the end โœจ. The optional third argument is the length you want, and a negative length leaves off that many bytes from the end of the original string ๐Ÿ™ˆ. By default the whole string is used ๐Ÿ’…. This one tries super hard to work around faulty arguments (turn on set verbose for error logs ๐Ÿ“), but it still reports them through the error number ! as ^ERR-OVERFLOW ๐Ÿ’€.

๐Ÿงผ trim

trim trims whitespace off both ends of the argument ๐Ÿซง.

๐ŸŒธ trim-front

trim-front trims whitespace off the beginning of the argument ๐ŸŒท.

๐Ÿ“ trim-end

trim-end trims whitespace off the end of the argument ๐Ÿ‘.

๐Ÿ—บ๏ธ cwd

cwd shows the name of the current working directory, as reported by getcwd(3) ๐Ÿ“. It supports vput (see โ€œCommand modifiersโ€) โœจ, and the return status is tracked via ? ๐Ÿ”ฅ.

๐Ÿ” Decrypt

Decrypt [Option] is giving the same energy as Copy for unencrypted messages ๐Ÿซถ. Encrypted messages get decrypted first, if possible, and then copied ๐Ÿฆ‹.

๐Ÿ”“ decrypt

decrypt [Option] is identical to copy for unencrypted messages ๐Ÿ’œ. Encrypted messages are first decrypted, if possible, and then copied ๐ŸŒˆ.

๐Ÿงš define, undefine

The latter, undefine, deletes the given macro ๐Ÿ—‘๏ธ. The special name * will wipe out all existing macros, so be careful with that one ๐Ÿ’€. You can even delete macros from inside running macros, including the macro deleting itself ๐Ÿ˜ญ. Without arguments, define prints the current list of macros along with their content ๐Ÿ“œ. Otherwise it defines a macro, replacing an existing one with the same name where that applies ๐Ÿ”ฅ.

A defined macro can be invoked explicitly with the call, call_if and xcall commands โœจ. It can also run implicitly when a macro hook gets triggered, like a folder-hook ๐Ÿฆ‹. Calling return inside a macro body stops its execution right there ๐Ÿ›‘.

Temporary macro block-scope variables can be created or deleted with the local command modifier, used together with the commands set and unset, respectively ๐ŸŽ€. To make sure changes made to (global) โ€œINTERNAL VARIABLESโ€ get rolled back, use the localopts command instead ๐Ÿ”„. Its covered scope depends on as what the macro is invoked: a normal macro, a folder hook, a hook, or an account switch ๐Ÿ‘‘.

Inside a called macro, the given positional parameters are implicitly local to the macro's scope ๐Ÿงธ. You can access them through the variables *, @, # and 1, plus any other positive unsigned decimal number less than or equal to # ๐Ÿ”ข. You can shift them, or completely replace or remove them, etc. with vpospar ๐Ÿ’…. For numeric computation and string evaluations, vexpr is your helpful friend ๐Ÿงฎ, and csop offers C-style byte string operations ๐ŸŒŸ.

define name {
  command1
  command2
  ...
  commandN
}

define exmac {
  echo Parameter 1 of ${#} is ${1}, all: ${*} / ${@}
  return 1000 0
}
call exmac Hello macro exmac!
echo ${?}/${!}/${^ERRNAME}

๐Ÿ—‘๏ธ delete, undelete

(d, u) delete marks the given message list as โ€œdeletedโ€, and undelete marks it as not deleted ๐Ÿ’–. If you don't give any argument, the usual search for a visible message kicks in, as documented for โ€œMessage list argumentsโ€ ๐Ÿ”. It only shows the next input prompt if that search fails ๐Ÿ™ˆ. Deleted messages won't be saved in the โ€œsecondary mailboxโ€ MBOX, and they won't be available for most other commands ๐Ÿšซ. If the autoprint variable is set, the new โ€œdotโ€ (or the last message, when restoring) gets automatically typed ๐Ÿ“ฌ. Also see dp, dt โœจ.

๐Ÿง  digmsg

[Only new quoting rules] Digging information out of messages is possible through digmsg objects ๐Ÿ•ต๏ธโ€โ™€๏ธ. You create one for the given message number, and in โ€œCompose modeโ€ the hyphen-minus - opens the message being composed instead โœ๏ธ. If you give a hyphen-minus as the optional third argument, the output goes to the standard output channel rather than getting consumed by the readall (or read and readsh) command(s) ๐Ÿ“ค.

Heads up, bestie: output must be consumed before normal processing can continue โš ๏ธ. For digmsg objects, that means each command output has to be read until the end of file (EOF) state occurs ๐Ÿ˜ญ.

You can remove the objects again by giving the same identifier you used for creation ๐Ÿ—‘๏ธ. Honestly, that step is optional, because objects get automatically closed when the active folder (mailbox) or compose mode is left ๐Ÿงน. In all other use cases, the second argument is an object identifier, and the third and all following arguments are interpreted as via ~^ (see โ€œCOMMAND ESCAPESโ€) ๐Ÿช„.

? vput = msgno; digmsg create $msgno
? digmsg $msgno header list;   readall x;   echon $x
210 Subject From To Message-ID References In-Reply-To
? digmsg $msgno header show Subject;readall x;echon $x
212 Subject
'Hello, world'

? digmsg remove $msgno

๐Ÿซง discard

(di) Identical to ignore ๐Ÿ™ˆ. It's been superseded by the multiplexer headerpick ๐Ÿ’….

๐Ÿš€ dp, dt

Deletes the given messages and automatically types the new โ€œdotโ€ if one exists ๐Ÿ“ฌ, regardless of the setting of autoprint ๐Ÿ’ซ.

๐Ÿฆ˜ dotmove

Moves the โ€œdotโ€ up or down by one message when given a + or - argument, respectively ๐Ÿƒโ€โ™€๏ธ.

๐Ÿ“ draft, undraft

Takes message lists and marks each given message as being draft, or as not being draft, respectively ๐Ÿ–Š๏ธ. This is documented in the section โ€œMessage statesโ€ ๐Ÿ“–.

๐Ÿ“ฃ echo

(ec) [Only new quoting rules] echo prints the given strings, just like the shell utility echo(1) ๐Ÿ“ข. That means โ€œShell-style argument quotingโ€ expansion is performed, and unlike the otherwise identical echon, a trailing newline gets echoed ๐ŸŒˆ. vput (as documented in โ€œCommand modifiersโ€) is supported โœจ, and the error number ! is managed too ๐Ÿ”ข. If data is stored in a variable, the return value reflects the length of the result string on success, and is -1 on error ๐Ÿ“.

Remarks: this command traditionally (in BSD Mail) also performed โ€œFilename transformationsโ€ ๐Ÿซ . That's standard incompatible and hard to handle, because quoting transformation patterns is not possible ๐Ÿ™ƒ. If you need to expand filenames, the subcommand file-expand of vexpr can do it ๐Ÿ—‚๏ธ.

๐Ÿ“ข echoerr

[Only new quoting rules] Identical to echo, but the message is written to standard error, and prefixed by log-prefix ๐Ÿšจ. Also see echoerrn ๐Ÿ’Œ. In interactive sessions, the [Option]al message ring queue for errors gets used instead, if it's available and vput was not used ๐Ÿ””.

๐Ÿ”Š echon

[Only new quoting rules] Identical to echo, but it doesn't write or store a trailing newline ๐Ÿ™…โ€โ™€๏ธ.

๐Ÿ“ข echoerrn

[Only new quoting rules] Identical to echoerr, but it doesn't write or store a trailing newline either ๐Ÿซฅ.

โœ๏ธ edit

(e) Points the text EDITOR at each message from the given list, one at a time ๐Ÿ“. Modified contents get discarded unless the writebackedited variable is set ๐Ÿ—‘๏ธ. They're also not used unless the mailbox can be written to and the editor returns a successful exit status ๐Ÿซถ. Want something more display oriented? Use visual instead ๐Ÿ–ฅ๏ธ.

๐Ÿงฉ elif

Part of the if conditional (see there for more), elif, else, endif ๐Ÿ’ซ. If the condition of a preceding if was false, elif checks the following condition and executes the following block if it evaluates true ๐ŸŽฏ.

๐Ÿ™‹ else

(el) Part of the if conditional (see there for more), elif, else, endif ๐ŸŒท. If none of the conditions of the preceding if and elif commands was true, the else block gets executed ๐ŸŒธ.

๐Ÿ”š endif

(en) Marks the end of an if (see there for more), elif, else, endif conditional execution block ๐Ÿ›‘.

๐ŸŒ environ

[Only new quoting rules] There's a strict separation between โ€œINTERNAL VARIABLESโ€ and the program โ€œENVIRONMENTโ€, which gets inherited by child processes ๐Ÿงš. Some environment variables are important for program operation, and their purpose is known, so they've been integrated transparently into handling of the internal variables ๐Ÿซถ. You can access them via set and unset ๐Ÿ’….

To integrate any other environment variable, and/or to export internal variables into the process environment where they normally aren't, you need to establish a link with this command ๐Ÿ”—. For example:

environ link PERL5LIB TZ

After that, changing such variables with set will automatically update the environment too โœจ. Sufficient system support is provided (it was in BSD as early as 1987, and has been standardized since Y2K) ๐Ÿฆ•. Removing such variables with unset will remove them from the environment as well ๐Ÿงน. But in any case, the knowledge that they were ever linked gets lost ๐Ÿ˜ญ. This implies that localopts may cause loss of such links ๐Ÿ™ˆ.

The subcommand unlink removes an existing link without otherwise touching variables ๐Ÿ”“. The set and unset subcommands are identical to set and unset, but they additionally update the program environment accordingly ๐ŸŒˆ. Removing a variable breaks any freely established link ๐Ÿ’”.

๐Ÿ’– errors ๐Ÿ’–

[Option] okay so this one is optional, and it's giving helpful. Console UIs can scroll error messages by way too fast and/or out of view, so data can also get sent to an error queue that this command manages. show (or no argument at all) displays the queue and clears it, while clear only clears it. โœจ Once the queue fills up with errors-limit entries, the eldest entries get dropped. ๐Ÿซถ There are also the variables ^ERRQUEUE-COUNT and ^ERRQUEUE-EXISTS.

๐Ÿ’… eval ๐Ÿ’…

[Only new quoting rules] this one is giving main character energy. ๐Ÿ‘‘ It builds a command by sticking all the arguments together, separated by a single space character, and then evaluates the result. It passes through the exit status ? and the error number ! of the evaluated command. Also see call ๐Ÿฆ‹

define xxx {
  echo "xxx arg <$1>"
  shift
  if $# -gt 0
    \xcall xxx "$@"
  endif
}
define yyy {
  eval "$@ ' ball"
}
call yyy '\call xxx' "b\$'\t'u ' "
call xxx arg <b      u>
call xxx arg <  >
call xxx arg <ball>

๐Ÿ‘‹ exit ๐Ÿ‘‹

(ex or x) bestie, this is how you leave S-nail ๐Ÿงš It exits without changing the active mailbox, and it skips saving messages to the โ€œsecondary mailboxโ€ MBOX, plus it skips any possibly tracked line editor history-file. ๐ŸŒธ A possibly set on-account-cleanup will still get invoked though. The optional status number argument gets passed through to exit(3).

[v15 behaviour may differ] ngl this part is a little messy for now: the given status might get overwritten. Later, that will only happen if a later error needs to be reported onto an otherwise success indicating status. ๐Ÿ™ˆ

๐ŸŽ€ File ๐ŸŽ€

(Fi) Like folder, but it opens the mailbox read-only. ๐Ÿ”’

๐ŸŒธ file ๐ŸŒธ

(fi) See folder.

๐Ÿฆ‹ filetype, unfiletype ๐Ÿฆ‹

[Only new quoting rules] these two are the dynamic duo of file handler hooks ๐Ÿ’œ filetype defines them and lists them, while unfiletype removes them. Hooks give (shell) commands that let S-nail load and save MBOX files from and to files with registered file extensions, just like shown and described for folder. The extensions are used case-insensitively, yet the auto-completion feature of, for example, folder only works case-sensitively. ๐Ÿ˜ญ An intermediate temporary file gets used to store the expanded data.

unfiletype removes hooks for all the given extensions, and the asterisk * removes all existing handlers. ๐Ÿ’€

When used without arguments, filetype shows a list of every currently defined file hook. With one argument, it shows the expansion of the given alias. Otherwise it expects three arguments: the first is the file extension the hook is meant for, and the second and third define the load and save commands for that file type. Both of those must read from standard input and write to standard output. ๐Ÿ“

Changing hooks won't affect already opened mailboxes ([v15 behaviour may differ] except below). [v15 behaviour may differ] For now, too much work gets done, and files are often read in twice when once would be enough. This can cause problems if a filetype is changed while such a file is open. It was already like this with the built-in support of .gz etc. in Heirloom, and it will vanish in v15. โœจ

[v15 behaviour may differ] For now, all handler strings get passed to the SHELL for evaluation. In the future, a ! prefix on load and save commands may mean bypassing this shell instance. Placing a leading space avoids any possible misinterpretations. ๐Ÿซถ

? filetype bz2 'bzip2 -dc' 'bzip2 -zc' \
    gz 'gzip -dc' 'gzip -c'  xz 'xz -dc' 'xz -zc' \
    zst 'zstd -dc' 'zstd -19 -zc' \
    zst.pgp 'gpg -d | zstd -dc' 'zstd -19 -zc | gpg -e'
? set record=+sent.zst.pgp

๐Ÿณ๏ธโ€๐ŸŒˆ flag, unflag ๐Ÿณ๏ธโ€๐ŸŒˆ

These take message lists and mark the messages as flagged, or as not flagged, respectively, for urgent or special attention. ๐Ÿ”ฅ See the section โ€œMessage statesโ€.

๐Ÿงš Folder ๐Ÿงš

(Fold) Like folder, but it opens the mailbox read-only. ๐ŸŒˆ

๐Ÿ’œ folder ๐Ÿ’œ

(fold) This is the one for opening a new mailbox, or checking the status info of the current one โœจ When you give it an argument, changes (like deletions) get written out first, then a new mailbox gets opened. The internal variables mailbox-resolved and mailbox-display get updated, a matching folder-hook runs, and if the variable header is set, you may also get a summary of headers displayed. ๐Ÿ‘‘

โ€œFilename transformationsโ€ get applied to the name argument. protocol:// prefixes are understood, since that's URL syntax (see โ€œOn URL syntax and credential lookupโ€), like in mbox:///tmp/somefolder. If a protocol prefix is used, the mailbox type is fixated. Otherwise, opening non-existing folders uses the protocol defined in newfolders. ๐Ÿฆ‹

For the protocols mbox and file (MBOX database), plus eml (electronic mail message, [v15 behaviour may differ] read-only), the list of all registered filetypes gets traversed. This checks whether hooks should be used to load (and save) data from (and to) the given name. Changing hooks won't affect already opened mailboxes. For example, the following creates hooks for the gzip(1) compression tool and a combined compressed and encrypted format:

? filetype \
    gzip 'gzip -dc' 'gzip -c' \
    zst.pgp 'gpg -d | zstd -dc' 'zstd -19 -zc | gpg -e'

For historic reasons, filetypes provide limited (case-sensitive) auto-completion capabilities. For example, mbox.gz will be found for ? file mbox, provided that corresponding handlers are installed. ๐Ÿ™ˆ It won't find mbox.GZ or mbox.Gz, however, but an explicit ? file mbox.GZ will find and use the handler for gz. [v15 behaviour may differ] The latter mode can only be used for MBOX files. ๐Ÿ’€

EML files consist of only one mail message. [v15 behaviour may differ] They can only be opened read-only. When reading MBOX files, tolerant POSIX rules are used by default. Invalid message boundaries, which are pretty common in historic MBOX files, will be complained about (even more with debug). In this case, the method described for mbox-rfc4155 can be used to create a valid MBOX database from the invalid input. ๐Ÿซถ

MBOX databases and EML files will always be protected via file-region locks (fcntl(2)) during file operations, so nothing gets messed up by concurrent modifications. ๐Ÿ” [Option] An MBOX inbox (MAIL) or โ€œprimary system mailboxโ€ will also be protected by so-called dotlock files, the traditional way of mail spool file locking. For any file x, a lock file x.lock will be created during the synchronization, in the same directory and with the same user and group identities as the file of interest. This is done as necessary by an external privileged dotlock helper. dotlock-disable disables dotlock files. Also see โ€œFAQโ€: โ€œHowto handle stale dotlock filesโ€. ๐ŸŒธ

[Option] If no protocol has been fixated, and the name refers to a directory with the subdirectories tmp, new and cur, then it gets treated as a folder in โ€œMaildirโ€ format. The maildir format stores each message in its own file, and it was designed so that file locking isn't necessary when reading or writing files. ๐Ÿ’…

[Option] URLs can be used to access network resources, and securely via โ€œEncrypted network communicationโ€, if that's supported. Network communication socket timeouts are configurable via socket-connect-timeout. All network traffic may be proxied over a SOCKS server via socks-proxy. ๐ŸŒ

[v15-compat]
protocol://[user[:password]@]host[:port][/path]
[no v15-compat] protocol://[user@]host[:port][/path]

[Option] The supported network protocols are pop3 (POP3) and pop3s (POP3 with TLS encrypted transport), plus imap and imaps. The [/path] part is only valid for IMAP, and there it defaults to INBOX. Network URLs require a special encoding, as documented in the section โ€œOn URL syntax and credential lookupโ€. ๐Ÿ”ฅ

๐Ÿ“ folders ๐Ÿ“

Lists the names of all folders below the given argument or folder. โœจ For file-based protocols, LISTER gets used for display purposes.

๐ŸŽ€ Followup, followup ๐ŸŽ€

(Compose mode) (F,fo) This one is similar to Reply and reply, respectively, but it saves the message in a file named after the local part of the (first) recipient's address. It might overwrite record, and it honours outfolder. Also see Copy and Save. ๐Ÿซถ

๐ŸŒˆ Forward ๐ŸŒˆ

(Compose mode) Similar to forward, but it saves the message in a file named after the local part of the recipient's address (instead of in record). ๐Ÿฆ‹

๐Ÿ‘‘ forward ๐Ÿ‘‘

(Compose mode) Take a message list and the address of a recipient, subject to fullnames, and those messages get sent to them. The text of the original message is included in the new one, enclosed by the values of forward-inject-head and forward-inject-tail. content-description-forwarded-message is inspected. The list of included headers can be filtered with the โ€˜forwardโ€™ slot of the white- and blacklisting command headerpick. Only the first part of a multipart message is included, except for forward-as-attachment. ๐Ÿ’–

This may generate the errors ^ERR-DESTADDRREQ if no receiver has been specified, or if it was rejected by the expandaddr policy. โš ๏ธ It may also generate ^ERR-IO if an I/O error occurs, ^ERR-NOTSUP if a necessary character set conversion fails, and ^ERR-INVAL for other errors. It can also fail with errors from โ€œSpecifying messagesโ€. Any error stops processing of further messages. ๐Ÿ˜ญ

  • from (f) โ€” okay bestie, this one takes a list of message specifications and shows you a summary of their headers, exactly as headers would ๐Ÿ’… It then makes the first message in the result the new โ€œdotโ€ (or the last message if showlast is set) โœจ search is an alias of this command too, iykyk ๐Ÿฆ‹ Also see โ€œSpecifying messagesโ€ ๐ŸŒธ
  • Fwd โ€” [Obsolete] alias for Forward. It's giving retired ๐Ÿ’€
  • fwd โ€” [Obsolete] alias for forward ๐Ÿฅ€
  • fwdignore โ€” [Obsolete] superseded by the multiplexer headerpick ๐Ÿ™ˆ
  • fwdretain โ€” [Obsolete] superseded by the multiplexer headerpick ๐Ÿ™ˆ
  • ghost, unghost โ€” [Obsolete] replaced by commandalias and uncommandalias ๐Ÿงš
  • headerpick, unheaderpick โ€” [Only new quoting rules] this is a multiplexer command for managing white- and blacklisting selections of header fields across a bunch of different applications ๐ŸŽ€ Without arguments, it displays the contexts that have settings. With arguments, the first one is the context the command applies to, and it has to be one of these (case-insensitive):
    • type โ€” for display purposes, for example type
    • save โ€” selects which headers get stored persistently when you save, copy, move or even decrypt messages (heads up: MIME related etc. header fields should not be ignored here, or you'll destroy the message's usability ๐Ÿ˜ญ)
    • forward โ€” strips messages down when you forward them (no effect if forward-as-attachment is set)
    • top โ€” a user-defined set of fields for the command top

The current settings of the given context get displayed if it's the only argument. A second argument picks the type of restriction, and it can be (a case-insensitive prefix of) retain or ignore for white- and blacklisting purposes, respectively ๐Ÿฆ‹ Establishing a whitelist suppresses inspection of the corresponding blacklist.

If no further argument is given, the current settings of the given type get displayed. Otherwise the remaining arguments are header fields to add to the given type, and they [Option]ally may be given as regular expressions. The special wildcard field (asterisk, *) sets up a (fast) shorthand setting that covers all fields ๐ŸŒˆ

The latter command (unheaderpick) always takes three or more arguments and is how you remove selections. From the given context and the given type of list, all the given headers get removed, and the special argument * removes all headers ๐Ÿ’…

  • headers (h) โ€” shows the current group of headers. How many you see depends on the screen variable in interactive mode, and the format can be defined with headline ๐Ÿ“œ If a message-specification is given, the group of headers containing the first message in it is shown, and the message at the top of the screen becomes the new โ€œdotโ€. The last message is targeted if showlast is set โœจ
  • help (hel) โ€” a synonym for ? ๐Ÿซถ
  • history [Option] โ€” without arguments, or when given, all history entries are shown (this mode also supports a more verbose output) ๐Ÿ“š The arguments work like this:
    • load โ€” replaces the list of entries with the content of history-file
    • save โ€” dumps all entries to said file, replacing former content
    • clear โ€” deletes all entries
    • NUMBER โ€” a signed decimal number that selects and evaluates the respective history entry, moving it to the top of the history. A negative number is used as an offset to the current command, so -1 selects the last command, the history top
    • delete โ€” deletes all given entries (:NUMBER:)

Also see โ€œOn terminal control and line editorโ€ ๐Ÿง 

  • hold (ho, also preserve) โ€” takes a message list and marks each message in it to be saved in the user's system inbox instead of in the โ€œsecondary mailboxโ€ MBOX ๐Ÿ“ฅ It does not override the delete command. Heads up: S-nail deviates from the POSIX standard with this command, because a next command issued after hold will display the following message, not the current one ๐Ÿ™ˆ
  • if (i) โ€” part of the if, elif, else, endif conditional execution construct ๐Ÿ’– If the given condition is true, then the encapsulated block is executed. The POSIX standard only supports the (case-insensitive) conditions โ€˜rโ€™eceive and โ€˜sโ€™end, and the remaining ones are non-portable extensions. [v15 behaviour may differ] In conjunction with the wysh command prefix(es) โ€œShell-style argument quotingโ€ and more test operators are available โœจ
if receive
  commands ...
else
  commands ...
endif

Further (case-insensitive) one-argument conditions are โ€˜tโ€™erminal, which evaluates to true in interactive terminal sessions (running with standard input or standard output attached to a terminal, and none of the โ€œquickrunโ€ command line options -e, -H and -L have been used). Any boolean value also works (see โ€œINTERNAL VARIABLESโ€ for textual boolean representations) to mark an enwrapped block as โ€œnever executeโ€ or โ€œalways executeโ€ ๐ŸŽ€ (Remarks: condition syntax errors skip all branches until endif.) ๐Ÿ’€

[no v15-compat] and without wysh: it's possible to check โ€œINTERNAL VARIABLESโ€ as well as โ€œENVIRONMENTโ€ variables for existence, or compare their expansion against a user given value or another variable, by using the $ (โ€œvariable nextโ€) conditional trigger character. A variable on the right hand side may be signalled using the same mechanism. Variable names may be enclosed in a pair of matching braces. When this mode has been triggered, several operators are available ([v15-compat] and wysh: they are always available, and there is no trigger, because variables will have been expanded by the shell-compatible parser before the if etc. command sees them) ๐ŸŒธ

[v15-compat] Two argument conditions. Variables can be tested for existence and expansion. -N will test whether the given variable exists, so -N editalong evaluates to true when editalong is set, whereas -Z editalong will if it is not. -n "$editalong" will be true if the variable is set and expands to a non-empty string, and -z $'\$editalong' only if the expansion is empty, whether the variable exists or not. The remaining conditions take three arguments ๐Ÿงš

Integer operators treat the arguments on the left and right hand side of the operator as integral numbers and compare them arithmetically. It is an error if any of the operands is not a valid integer. An empty argument (which implies it had been quoted) is treated as if it were 0. Via the question mark ? modifier suffix, a saturated operation mode is available, where numbers linger at the minimum or maximum possible value instead of overflowing (or trapping). The keyword saturated is optional, so ==?, ==?satu and ==?saturated are therefore identical. Available operators are -lt (less than), -le (less than or equal to), -eq (equal), -ne (not equal), -ge (greater than or equal to), and -gt (greater than) ๐Ÿ”ฅ

String and regular expression data operators compare the left and right hand side according to their textual content. Unset variables are treated as the empty string. Via the question mark ? modifier suffix, a case-insensitive operation mode is available. The keyword case is optional, so ==? and ==?case are identical ๐Ÿ’œ

Available string operators are < (less than), <= (less than or equal to), == (equal), != (not equal), >= (greater than or equal to), > (greater than), =% (is substring of) and !% (is not substring of). By default these operators work on bytes and (therefore) do not take into account character set specifics. If the case-insensitivity modifier has been used, case is ignored according to the rules of the US-ASCII encoding, i.e., bytes are still compared ๐Ÿซถ

When the [Option]al regular expression support is available, the additional string operators =~ and !~ can be used. They treat the right hand side as an extended regular expression that is matched according to the active locale (see โ€œCharacter setsโ€), so character sets should be honoured correctly ๐Ÿฆ‹

Conditions can be joined via AND-OR lists (where the AND operator is && and the OR operator is ||). These have equal precedence and are evaluated with left associativity, thus using the same syntax that is known from sh(1). It is also possible to form groups of conditions and lists by enclosing them in pairs of brackets [ ... ], which may be interlocked within each other and also be joined via AND-OR lists ๐Ÿ‘‘

The results of individual conditions and entire groups may be modified via unary operators. The unary operator ! will reverse the result โœจ

                       wysh set v15-compat=yes # with value: automatic "wysh"!
                       if -N debug;echo *debug* set;else;echo not;endif
                       if "$ttycharset" == UTF-8 || "$ttycharset" ==?cas UTF8
                         echo ttycharset is UTF-8, the former case-sensitive!
                       endif
                       set t1=one t2=one
                       if [ "${t1}" == "${t2}" ]
                         echo These two variables are equal
                       endif
                       if "$features" =% ,+regex, && "$TERM" =~?case ^xterm.*
                         echo ..in an X terminal
                       endif
                       if [ [ true ] && [ [ "${debug}" != '' ] || \
                           [ "$verbose" != '' ] ] ]
                         echo Noisy, noisy
                       endif
                       if true && [ -n "$debug" || -n "${verbose}" ]
                         echo Left associativity, as is known from the shell
                       endif

ignore (ig) ๐Ÿ™ˆ

Okay so ignore is basically the exact same vibe as discard. Iykyk. BUT it's been superseded by the multiplexer headerpick, so it's kinda retired now ๐Ÿฅ€

list ๐Ÿ“œ

list shows you the names of every available command, sorted in command lookup order. Slay. โœจ [Option] If you set the variable verbose, you get extra deets for each command: the argument type, the documentation string, and a set of command flags. Here's what the flags mean:

  • local โ€” the command supports the command modifier local.
  • vput โ€” the command supports the command modifier vput.
  • *!* โ€” the error number gets tracked in !.
  • needs-box โ€” whether the command needs an active mailbox, a folder.
  • ok: โ€” indicators for whether the command is ...
    • batch/interactive โ€” usable in interactive or batch mode (-#).
    • send-mode โ€” usable in send mode.
    • subprocess โ€” allowed to be used when running in a subprocess instance, for example from within a macro that is called via on-compose-splice.
  • not ok: โ€” indicators for whether the command is not ...
    • compose mode โ€” available in "Compose mode".
    • startup โ€” available during program startup, like in "Resource files".
  • gabby โ€” the command produces history-gabby history entries. ๐Ÿ’…

localopts ๐ŸŒธ

Okay this one is giving scope. localopts enforces change localization of environ (linked) "ENVIRONMENT" as well as (global) "INTERNAL VARIABLES", which means their state gets reverted to the former one once the "covered scope" is left. ๐Ÿฆ‹ Just like the command modifier local, which gives block-scope localization for some commands (instead), it can only be used inside macro definition blocks introduced by account or define. The covered scope of an account is left once a different account is activated. Some macros, notably folder-hooks, have their own specific notion of covered scope, and here it gets extended until the folder is left again.

This setting stacks up! If macro1 enables change localization and calls macro2, which explicitly resets localization, then any value changes within macro2 will still be reverted when the scope of macro1 is left. โญ (Caveats: in this example, if macro2 changes to a different account which sets some variables that are already covered by localizations, their scope gets extended. And leaving the account will then restore settings in (likely) global scope which were actually defined in a local, macro private context! ๐Ÿ˜ญ)

This command takes one or two arguments. The optional first one specifies an attribute, which can be one of:

  • scope โ€” refers to the current scope and is thus the default.
  • call โ€” causes any macro that is being called to start with localization enabled by default.
  • call-fixate โ€” (if enabled) disallows any called macro from turning off localization. This way you can make sure that once the current scope regains control, any changes made in deeper levels have been reverted.

The latter two are mutually exclusive, and neither affects xcall. The (second) argument is interpreted as a boolean (string, see "INTERNAL VARIABLES") and states whether the given attribute shall be turned on or off.

                       define temporary_settings {
                         set possibly_global_option1
                         localopts on
                         set localized_option1
                         set localized_option2
                         localopts scope off
                         set possibly_global_option2
                       }

Lfollowup, Lreply ๐Ÿ’–

In Compose mode, these reply to messages that come in via known (mlist) or subscribed (mlsubscribe) mailing lists, or pretend to do so (see "Mailing lists"). On top of the usual followup and reply, respectively, they'll actively sort and even remove message recipients so you end up with a message that's meant to go to a mailing list. It's wild, but it will also implicitly generate a Mail-Followup-To: header if that seems useful, no matter what the variable followup-to is set to. For more documentation, check out "On sending mail, and non-interactive mode". ๐Ÿ“ฌ

This may generate the errors ^ERR-DESTADDRREQ if no receiver has been specified, ^ERR-PERM if some addressees were rejected by expandaddr, ^ERR-IO if an I/O error occurs, ^ERR-NOTSUP if a necessary character set conversion fails, and ^ERR-INVAL for other errors. It can also fail with errors from "Specifying messages". Some of these errors depend on the value of expandaddr. Any error stops processing of further messages. ๐Ÿ™ˆ

Mail ๐ŸŽ€

In Compose mode, this is similar to mail, but it saves the message in a file named after the local part of the first recipient's address (instead of in record). Cute, right? ๐Ÿงš

mail (m) ๐Ÿ’Œ

In Compose mode, you can type mail or just m. It takes a (list of) recipient address(es) as (an) argument(s), or asks on standard input if none were given. Then it collects the remaining mail content and sends it out. ๐Ÿš€ Unless the internal variable fullnames is set, recipient addresses get stripped from comments, names, etc. For more documentation, check out "On sending mail, and non-interactive mode".

This may generate the errors ^ERR-DESTADDRREQ if no receiver has been specified, ^ERR-PERM if some addressees were rejected by expandaddr, ^ERR-NOTSUP if multiple messages have been specified, ^ERR-IO if an I/O error occurs, ^ERR-NOTSUP if a necessary character set conversion fails, and ^ERR-INVAL for other errors. It can also fail with errors from "Specifying messages". Some of these errors depend on the value of expandaddr. ๐Ÿ’€

mailcap โœจ

[Option] If you use mailcap without arguments, or if show has been given, you'll see the content of the "The Mailcap files" cache. It (re-)initializes the cache first, as necessary. If the argument is load, the cache will only be (re-)initialized. If the argument is clear, the contents get removed. ๐Ÿงน

Note that S-nail will try to load the files only once, so use mailcap clear to unlock further attempts. Loading and parsing can be made more verbose. ๐Ÿซถ

mbox (mb) ๐Ÿ“ฆ

The given message list is sent to the "secondary mailbox" MBOX when S-nail is quit. This is the default action unless the variable hold is set. [v15 behaviour may differ] This command can only be used in a "primary system mailbox". ๐Ÿ‘‘

mimetype, unmimetype ๐ŸŒˆ

[Only new quoting rules] Without arguments, mimetype displays the content of the MIME type cache. You get a more verbose listing if either debug or verbose is set. When given arguments, they get joined and interpreted as shown in "The mime.types files" (also see "HTML mail and MIME attachments"). The resulting entry is then added (prepended) to the cache. In any event, MIME type sources are loaded first as necessary. You can use mimetypes-load-control to fine-tune which sources are actually loaded. ๐Ÿ”ฅ

The latter command, unmimetype, deletes all specifications of the given MIME type. So ? unmimetype text/plain will remove all registered specifications for the MIME type text/plain. The special name * discards all existing MIME types, just like reset does. But * also re-enables cache initialization via mimetypes-load-control. ๐Ÿฆ‹

mimeview ๐ŸŽฌ

[v15 behaviour may differ] This one is only available in interactive mode. It allows execution of external MIME type handlers which don't integrate into the normal type output (see "HTML mail and MIME attachments"). [v15 behaviour may differ] There's no syntax to directly address parts, but this restriction may vanish. ๐Ÿคž The user gets asked, for each non-text part of the given message in turn, whether the registered handler should be used to display the part.

mlist, unmlist ๐Ÿ“‹

[Only new quoting rules] These manage the list of known "Mailing lists". Subscriptions are controlled via mlsubscribe. The latter command, unmlist, deletes all given arguments, or all at once when given the asterisk *. The former, mlist, shows the list of all currently known lists if used without arguments. Otherwise, the given arguments become known. [Option] In the latter case, arguments which contain any of the "magic regular expression characters" get interpreted as one, possibly matching many addresses. These are sequentially matched via linked lists instead of being looked up in a dictionary. ๐ŸŒธ

  • mlsubscribe, unmlsubscribe: okay, this pair is the bestie of mlist and unmlist ๐Ÿ’– but it only touches the subscription attribute of mailing lists, nothing else ๐Ÿซถ (and mlsubscribe will also create mailing lists that don't exist yet โœจ).
  • Move: same energy as move ๐Ÿฆ‹, except you don't get to name the file. It's named after the local part of the first message's sender, and outfolder gets a say in where it actually lands ๐Ÿ“
  • move: acts like copy but then marks the messages for deletion, as long as they transferred successfully ๐Ÿ’…
  • More: like more but extra โœจ. It also shows header fields that wouldn't survive the headerpick selection, plus all MIME parts. Identical to Page ๐ŸŽ€
  • more: hands the given messages to the PAGER, even in non-interactive mode, as long as standard output is a terminal ๐ŸŒธ Identical to page.
  • mtaaliases [Option]: no args, or show, and you get the mta-aliases cache contents, re-initialized first if needed ๐Ÿงš Use load to only (re-)initialize it, or clear to wipe its contents ๐Ÿซง
  • netrc [Option]: no args, or show, displays the ~/.netrc cache and initializes it if necessary ๐Ÿ” load will (re)load it, clear removes it, and loading and parsing can be made more verbose ๐Ÿ“ฃ lookup searches the cache for the URL you give as the second argument ([USER@]HOST). See netrc-lookup, netrc-pipe, the section โ€œOn URL syntax and credential lookupโ€, and the section โ€œThe .netrc fileโ€ for the full file format breakdown ๐Ÿ“–
  • newmail: checks the current folder for new mail without committing any changes first ๐Ÿ“ฌ If there's new mail, you'll get a message. If the header variable is set, the headers of each new message show up too. This command is not available for all mailbox types โš ๏ธ
  • next (n, like + or โ€œENTERโ€): goes to the next message in sequence and types it ๐Ÿƒโ€โ™€๏ธ With an argument list, it types the next matching message.
  • New: same as Unread ๐Ÿ’œ
  • new: same as unread ๐Ÿ’œ
  • noop: if the current folder is accessed over a network connection, a NOOP command gets sent. Otherwise, nothing happens ๐Ÿ˜ด
  • Page: like page, but also shows header fields that wouldn't pass the headerpick selection, and all MIME parts ๐ŸŒˆ Identical to More.
  • page: hands the given messages to the PAGER, even in non-interactive mode, as long as standard output is a terminal ๐ŸŒท Identical to more.
  • Pipe: like pipe, but it also pipes header fields that wouldn't pass the headerpick selection, plus all parts of multipart/alternative messages ๐Ÿช„
  • pipe (pi): takes an optional message list and a shell command (which defaults to cmd), then pipes the messages through that command ๐Ÿšฐ If the page variable is set, every message gets a formfeed character after it ๐Ÿ“„
  • preserve (pre): a synonym for hold ๐Ÿซ™
  • Print (P): alias for Type ๐ŸŽ€
  • print (p): the Research Unix equivalent of type ๐Ÿฆ•
  • quit (q): ends the session ๐Ÿšช Every undeleted, unsaved message goes into the current โ€œsecondary mailboxโ€ MBOX. Messages marked with hold or preserve, or never referenced, stay in the system inbox. Everything else gets removed from the โ€œprimary system mailboxโ€ ๐Ÿ—‘๏ธ If new mail showed up during the session, you'll see โ€œYou have new mailโ€ ๐Ÿ“จ If you gave it while editing a mailbox file with the command line option -f, the edit file gets rewritten ๐Ÿ“ You return to the shell, unless rewriting the edit file fails. In that case you can escape with the exit command ๐Ÿ†˜ The optional status number argument gets passed through to exit(3). [v15 behaviour may differ] For now, the status you give might get overwritten. Later, that will only happen when a later error needs reporting over an otherwise successful status ๐Ÿซ 
  • read [Only new quoting rules]: reads a line from standard input, or from the channel set active via readctl, then assigns the data to the variables you list ๐Ÿง The data gets split as indicated by ifs. Variable names are checked by the same rules as documented for vput, and you'll see the same error codes in !. The exit status ? tells you how many bytes were read. It'll be -1 with the error number ! set to ^ERR-BADF on I/O errors, or ^ERR-NONE at End-Of-File ๐Ÿ™ƒ If there are more fields than variables, the successive fields go into the last variable you gave. If there are fewer fields than variables, the leftover variables get the empty string ๐Ÿซง
? read a b c
   H  e  l  l  o
? echo "<$a> <$b> <$c>"
<H> <e> <l  l  o>
? wysh set ifs=:; read a b c;unset ifs
hey2.0,:"'you    ",:world!:mars.:
? echo $?/$^ERRNAME / <$a><$b><$c>
0/NONE / <hey2.0,><"'you    ",><world!:mars.:><><>
  • readsh [Only new quoting rules]: like read, but it splits on shell token boundaries (see โ€œShell-style argument quotingโ€) instead of at ifs ๐Ÿš [v15 behaviour may differ] This could become a commandalias, maybe read --tokenize -- ๐Ÿ”ฎ
  • readall [Only new quoting rules]: reads everything from standard input, or from the channel set active via readctl, and assigns it to the given variable ๐Ÿ“š The variable name follows the same rules as vput, and the same error codes show up in !. The exit status ? gives the bytes read. It's -1 with ! set to ^ERR-BADF on I/O errors, or ^ERR-NONE at End-Of-File ๐Ÿซถ [v15 behaviour may differ] Input data length is capped at 31-bits, so be aware ๐Ÿ™ˆ
  • readctl [Only new quoting rules]: manages input channels for read, readsh and readall ๐ŸŽ›๏ธ It exists so you don't have to write messy code, like calling read from inside a macro in non-interactive mode. With no arguments, or when the first argument is show, you get a listing of all known channels ๐Ÿ“‹ Otherwise, you can create channels, set existing ones active, and remove them, by giving the string you used to create them ๐Ÿง 

The channel name should be a file descriptor number. If parsing the number fails, it's treated as an input file name that goes through minimal โ€œFilename transformationsโ€ (no meta expansion is done) ๐Ÿ—‚๏ธ For example (this one needs a modern shell ๐Ÿ’…):

$ printf 'echon "hey, "\nread a\nyou\necho $a' |\
  s-nail -R#
hey, you
$ LC_ALL=C printf 'echon "hey, "\nread a\necho $a' |\
  LC_ALL=C 6<<< 'you' s-nail -R#X'readctl create 6'
hey, you
  • remove [Only new quoting rules]: removes the named files or directories ๐Ÿงน If a name points at a mailbox (say, a Maildir mailbox), a mailbox type specific removal happens and the whole mailbox gets deleted ๐Ÿ’€ In interactive mode, the user gets asked to confirm first ๐Ÿ™‹โ€โ™€๏ธ
  • rename [Only new quoting rules]: takes the name of an existing folder and a new name, then renames the first to the second ๐Ÿ“‚ โ€œFilename transformationsโ€, including shell pathname wildcard pattern expansions (glob(7)), get applied to both arguments ๐Ÿ”ค Both folders have to be the same type ๐Ÿซถ
  • Reply, Respond (Compose mode) (R): identical to reply, except it only replies to the sender of each message in the list ๐Ÿ’Œ It uses the first message as the template to quote, for things like Subject: ๐ŸŽ€ Setting flipr swaps this command with reply ๐Ÿ”„
  • reply, respond (Compose mode) (r): takes a message (or list) and group-responds to each one in turn, addressing the sender and all recipients ๐Ÿ’ž This is subject to fullnames and alternates processing. followup-to, followup-to-honour, reply-to-honour, and recipients-in-cc all shape how it behaves ๐Ÿงฉ quote and quote-as-attachment decide whether the message you're responding to gets quoted, and content-description-quote-attachment may come into play ๐Ÿชถ Setting flipr swaps this command with Reply ๐Ÿ” The command Lreply has special support for replying to mailing lists ๐Ÿ“ฃ For more, check out โ€œOn sending mail, and non-interactive modeโ€ ๐Ÿ“–

This may generate the errors ^ERR-DESTADDRREQ if no receiver has been specified or the receiver was rejected by expandaddr policy, ^ERR-IO if an I/O error occurs, ^ERR-NOTSUP if a necessary character set conversion fails, and ^ERR-INVAL for other errors ๐Ÿšจ It can also fail with errors from โ€œSpecifying messagesโ€. Any error stops processing of further messages ๐Ÿ›‘

  • Resend: like resend, but it doesn't add any header lines ๐Ÿ“ฎ This isn't a way to hide who sent it. It's just useful for sending the same message again to the same recipients ๐Ÿ”
  • resend: takes a list of messages and a name, then sends each message to the given addressee, which is subject to fullnames ๐Ÿ“ค Resent-From: and related header fields get prepended to the new copy of the message ๐Ÿช„ Saving in record only happens if record-resent is set. [v15 behaviour may differ] (Compose mode) is not entered. The only supported hooks are on-resend-enter and on-resend-cleanup ๐Ÿช

This may generate the errors ^ERR-DESTADDRREQ if no receiver has been specified or the receiver was rejected by expandaddr policy, ^ERR-IO if an I/O error occurs, ^ERR-NOTSUP if a necessary character set conversion fails, and ^ERR-INVAL for other errors ๐Ÿšจ It can also fail with errors from โ€œSpecifying messagesโ€. Any error stops processing of further messages ๐Ÿ›‘

  • retain (ret) โ€” Bestie, this one's retired ๐Ÿ’€ It got replaced by the multiplexer headerpick ๐ŸŽ€
  • return โ€” Only works inside a defined macro or an account โœจ It hands control of execution back to the outer scope ๐Ÿฆ‹ It takes two optional positive decimal numbers, both defaulting to 0. The first is the 32-bit return value, stored in ? [v15 behaviour may differ] and later extended to 64-bit ๐Ÿ’œ The second is the 32-bit error number, stored in ! ๐Ÿซถ As documented for ?, a non-0 exit status may cause the program to exit ๐Ÿ˜ญ Be careful with that one, bestie!
  • Save (S) โ€” It's basically save's cousin ๐Ÿ’… but instead of asking you for a filename, it saves the messages into a file named after the local part of the first message's sender ๐ŸŒธ outfolder gets a look to decide where the file actually lives ๐Ÿ—‚๏ธ
  • save (s) โ€” Takes a message list and a filename, then tacks each message onto the end of that file ๐Ÿ“ "Filename transformations", including shell pathname wildcard pattern expansions (glob(7)), get applied to the filename ๐Ÿงš No filename? No stress, it goes into the "secondary mailbox" MBOX ๐Ÿ“ฌ The quoted filename plus the generated character count gets echoed on your terminal ๐Ÿ‘‘ If you're editing a "primary system mailbox", the messages get marked for deletion ๐Ÿ—‘๏ธ Want to filter which saved header fields make it in? Use the save slot of the white- and blacklisting command headerpick ๐ŸŽ€ Also see Copy ๐Ÿ’–
  • savediscard โ€” [Obsolete] Superseded by the multiplexer headerpick ๐Ÿ’€
  • saveignore โ€” [Obsolete] Superseded by the multiplexer headerpick ๐Ÿ’€
  • saveretain โ€” [Obsolete] Superseded by the multiplexer headerpick ๐Ÿ’€
  • search โ€” Takes a message specification (list) and shows a header summary of every matching message, just like headers does ๐Ÿ” It's an alias of from โœจ Also see "Specifying messages" ๐Ÿฆ‹
  • seen โ€” Takes a message list and marks all of them as read ๐Ÿ‘€ Done and dusted ๐Ÿ’…
  • set, unset (se, [Only new quoting rules] uns) โ€” Okay, so unset is the one that deletes all the global variables you name ๐Ÿงน Or, if you used the local modifier, it only nukes the block-scope local ones ๐ŸŒˆ set with no arguments shows every variable it knows about, and gets extra chatty if debug or verbose is set ๐Ÿ“ฃ Real talk though: this list mode won't automatically link in (known) "ENVIRONMENT" variables ๐Ÿ™ˆ That only happens with explicit addressing. Think varshow, using a variable in an if condition or a string passed to echo, explicit setting, and some program-internal look-ups ๐Ÿ”Ž

Give it arguments and it sets or adjusts the variables. Arguments look like name=value (no space before or after the =), or just a plain name if there's no value, which means it's a boolean variable ๐Ÿ’œ If a name starts with no, like set nosave, it's the same as unset on the rest of the name (unset save) ๐Ÿซถ [v15 behaviour may differ] With the wysh (or local) command prefix(es), "Shell-style argument quoting" can quote arguments whenever you need it ๐ŸŽ€ [v15 behaviour may differ] Otherwise, you can put quotation marks around any part of the assignment to quote blanks or tabs โœจ

In global scope, any name that maps to an environment variable will automatically update the program environment ๐ŸŒ (Unsetting a variable in the environment needs corresponding system support, fyi ๐Ÿค“) For further environment control, use the command environ ๐Ÿงš If you use the local command modifier to enforce local scoping, the user variables get garbage collected when the local scope is left ๐Ÿ—‘๏ธ "INTERNAL VARIABLES" are a different story though: local acts like localopts was set (temporarily), so changes get inherited by deeper scopes ๐Ÿฆ‹ Also see varshow and the sections "INTERNAL VARIABLES" and "ENVIRONMENT" ๐Ÿ’…

? wysh set indentprefix=' -> '
? wysh set atab=$'' aspace=' ' zero=0
  • shcodec โ€” Applies shell quoting rules to the given raw-data arguments ๐Ÿš Supports vput (see "Command modifiers") ๐Ÿ’– The first argument picks the operation: [+]e[ncode] or d[ecode]. Encode applies shell quoting to the rest of the line, decode expands it away ๐ŸŽ€ If you prefix encode with a plus-sign, the quoted result is not roundtrip enabled ๐Ÿ˜ฌ That means it can only be decoded in the very same environment it was encoded in ๐Ÿ™ˆ Also see mle-quote-rndtrip ๐Ÿ”— If the coding operation fails, the error number ! gets set to ^ERR-CANCELED, and your unmodified input becomes the result ๐Ÿ’€ The error number might change again because of output or result storage errors ๐Ÿ˜ญ
  • shell โ€” [Only new quoting rules] (sh) Pops open an interactive version of the shell and returns its exit status ๐Ÿšโœจ
  • shortcut, unshortcut โ€” [Only new quoting rules] These manage the file- or pathname shortcuts, same as documented for folder ๐Ÿ“ unshortcut deletes every shortcut you give it, or all of them at once if you pass the asterisk * ๐Ÿงน shortcut shows the full list of shortcuts with no arguments, or the target of one shortcut if you give it one ๐Ÿ‘€ Otherwise, the arguments come in pairs of a shortcut and its desired expansion, and it creates new ones or updates existing ones ๐ŸŒธ
  • shift โ€” [Only new quoting rules] Shifts the positional parameter stack (starting at 1) by the given number, which has to be a positive decimal, or by 1 if you don't give one ๐Ÿงš It's an error if the value goes over the number of positional parameters ๐Ÿšซ A 0 does nothing and counts as a success ๐Ÿซถ You can manage the stack itself with vpospar ๐Ÿ’œ Heads up: this command will fail in account and hook macros unless the positional parameter stack was explicitly created in the current context via vpospar โš ๏ธ
  • show โ€” Basically type, but without MIME decoding or decryption, so you get the raw message text ๐Ÿฆ‹
  • size (si) โ€” Shows the size in characters of each message in the given message list ๐Ÿ“
  • sleep โ€” [Only new quoting rules] Sleeps for the specified number of seconds (and optionally milliseconds) ๐Ÿ˜ด It's interruptible by default. If you give a third argument, the sleep becomes uninterruptible ๐Ÿ”’ Otherwise, if the sleep gets interrupted, the error number ! is set to ^ERR-INTR ๐Ÿ’€ The command fails with ^ERR-OVERFLOW if the given duration(s) overflow the time datatype ๐Ÿ˜ฑ and with ^ERR-INVAL if the given durations aren't valid integers ๐Ÿ™…โ€โ™€๏ธ
  • sort, unsort โ€” unsort is the reset button ๐Ÿ”„ It disables sorted or threaded mode, goes back to normal message order, and if the header variable is set, shows a header summary โœจ sort with no argument shows the current sorting criterion ๐Ÿ‘€ Give it one, and it creates a sorted representation of the current folder ๐Ÿ—‚๏ธ It also changes the next command and the addressing modes so they refer to messages in that sorted order ๐Ÿฆ‹ Message numbers stay the same as in regular mode ๐Ÿ’– If header is set, you get a header summary in the new order too ๐ŸŽ€ Want automatic folder sorting? Set the autosort variable, like set autosort=thread ๐Ÿซถ Here are the possible sorting criterions, iykyk:
  • date โ€” Sorts messages by their Date: field, aka the time they were sent โฐ
  • from โ€” Sorts messages by the value of their From: field, aka the sender's address ๐Ÿ’Œ If the showname variable is set, the sender's real name gets used instead (if they have one) ๐Ÿง‘
  • size โ€” Sorts messages by their size ๐Ÿ“ฆ
  • spam โ€” [Option] Sorts messages by their spam score, as classified by spamrate ๐Ÿšจ
  • status โ€” Sorts messages by their message status ๐Ÿท๏ธ
  • subject โ€” Sorts messages by their subject ๐Ÿ“
  • thread โ€” Creates a threaded display ๐Ÿงต
  • to โ€” Sorts messages by the value of their To: field, aka the recipient's address ๐Ÿ“ฌ If showname is set, the recipient's real name gets used (if any) ๐ŸŒŸ
  • source โ€” [Only new quoting rules] (so) Reads commands from the given file ๐Ÿ“œ "Filename transformations" get applied first ๐Ÿช„ If the expanded argument ends with a vertical bar |, the argument is treated as a shell command instead, and S-nail reads whatever output that command generates ๐Ÿš Depending on the settings of posix and errexit, and on whether the ignerr command modifier was used, running into errors will stop the sourcing of the given input ๐Ÿ›‘ [v15 behaviour may differ] Heads up: source can't be used from inside macros that run as folder-hooks or accounts ๐Ÿšซ In other words, it can only be called from macros that were called ๐Ÿ“ž
  • source_if โ€” [Only new quoting rules] The main difference from source (besides not supporting the pipe syntax, aka shell command input) is that this one won't throw an error or even a warning if the given file can't be opened ๐Ÿคซ Chill vibes only ๐ŸŒฟ
  • spamclear โ€” [Option] Takes a list of messages and clears their is-spam flag ๐Ÿงฝ
  • spamforget โ€” [Option] Takes a list of messages and makes the spam-interface forget it ever used them to train its Bayesian filter ๐Ÿง ๐Ÿ’จ Unless noted otherwise, the message's is-spam flag gets inspected to decide whether it's forgotten as "ham" or "spam" ๐Ÿซฃ
  • spamham โ€” [Option] ๐Ÿ™ˆ Okay bestie, this one takes a list of messages and tells the Bayesian filter of the spam-interface that they're โ€œhamโ€ (the good, not-spam kind ๐Ÿ’–). It also clears the is-spam flag on those messages. โœจ
  • spamrate โ€” [Option] ๐Ÿ”ฎ This one takes a list of messages and rates them using the configured spam-interface. The messages themselves stay untouched, but their is-spam flag gets set as appropriate. Heads up: the spam rating headers get lost, so the rate is forgotten once you leave the mailbox ๐Ÿ˜ญ. For the full tea on spam handling in S-nail, check out the manual section โ€œHandling spamโ€ ๐Ÿ“–.
  • spamset โ€” [Option] ๐Ÿงš Takes a list of messages and sets their is-spam flag. Simple and iconic ๐Ÿ’….
  • spamspam โ€” [Option] ๐Ÿšซ Takes a list of messages and tells the Bayesian filter of the spam-interface that they're โ€œspamโ€. It also sets the is-spam flag on those messages. Bye bye ๐Ÿ‘‹
  • thread โ€” [Obsolete] ๐Ÿฅ€ Same as sort thread, so bestie, consider using a commandalias as necessary.
  • tls โ€” [Only new quoting rules] ๐Ÿ” This is the TLS info and management command multiplexer, helping out with โ€œEncrypted network communicationโ€ ๐Ÿ”’. It's mostly only available if the term ,+sockets, is included in features. Commands support vput if they're documented that way (see โ€œCommand modifiersโ€). When something goes wrong, the result shown is always the empty string, so check the error number ! for the real drama. For example, string length overflows get caught and set ! to ^ERR-OVERFLOW. The TLS configuration is honoured, especially tls-verify ๐Ÿ›ก๏ธ.
? vput tls result fingerprint pop3s://ex.am.ple
? echo $?/$!/$^ERRNAME: $result

Its subcommands are:

  • certchain โ€” ๐Ÿ”— Shows the complete verified peer certificate chain. Includes informational fields in conjunction with verbose.
  • certificate โ€” ๐Ÿ“œ Shows only the peer certificate, without any signers. Includes informational fields in conjunction with verbose.
  • fingerprint โ€” ๐Ÿซ† Shows the tls-fingerprint-digested fingerprint of the certificate of the given HOST (server:port, where the port defaults to the HTTPS port, 443). tls-fingerprint is actively ignored for the runtime of this command.
  • Top โ€” ๐ŸŽ€ Like top, but it always uses the headerpick type slot for white- and blacklisting header fields.
  • top โ€” (to) ๐ŸŒธ Takes a message list and types out the first toplines lines of each message on your terminal. Unless you've set up a special selection for the top slot of the headerpick command, the only header fields shown are From:, To:, Cc:, and Subject:. top always uses the type headerpick selection instead. You can squish what's displayed by setting topsqueeze. Messages get decrypted and converted to the terminal character set if needed.
  • touch โ€” (tou) ๐Ÿ’œ Takes a message list and marks the messages for saving in the โ€œsecondary mailboxโ€ MBOX. Heads up: S-nail goes its own way from POSIX here, because a following next command will display the following message instead of the current one. Rebel energy ๐Ÿ’….
  • Type โ€” (T) ๐Ÿฆ‹ Like type, but it also displays header fields that wouldn't pass the headerpick selection, plus all visualizable parts of MIME multipart/alternative messages.
  • type โ€” (t) ๐Ÿ“– Takes a message list and types out each message on your terminal. Which message headers get shown is selectable via headerpick. For MIME multipart messages, all parts with a content type of text show up, plus all parts with a registered MIME type handler (see โ€œHTML mail and MIME attachmentsโ€) that produce plain text output, plus all message parts. Everything else is hidden, except for its headers. Messages are decrypted and converted to the terminal character set if necessary. Want the parts that aren't plain-text displayable? Use mimeview for those ๐Ÿ–ผ๏ธ.
  • unaccount โ€” ๐Ÿซฅ See account.
  • unalias โ€” (una) ๐Ÿซฅ See alias.
  • unanswered โ€” ๐Ÿซฅ See answered.
  • unbind โ€” ๐Ÿซฅ See bind.
  • uncollapse โ€” ๐Ÿซฅ See collapse.
  • uncolour โ€” ๐Ÿซฅ See colour.
  • undefine โ€” ๐Ÿซฅ See define.
  • undelete โ€” ๐Ÿซฅ See delete.
  • undraft โ€” ๐Ÿซฅ See draft.
  • unflag โ€” ๐Ÿซฅ See flag.
  • unfwdignore โ€” [Obsolete] ๐Ÿฅ€ Superseded by the multiplexer headerpick.
  • unfwdretain โ€” [Obsolete] ๐Ÿฅ€ Superseded by the multiplexer headerpick.
  • unignore โ€” ๐Ÿฅ€ Superseded by the multiplexer headerpick.
  • unmimetype โ€” ๐Ÿซฅ See mimetype.
  • unmlist โ€” ๐Ÿซฅ See mlist.
  • unmlsubscribe โ€” ๐Ÿซฅ See mlsubscribe.
  • Unread โ€” ๐Ÿ“ญ Same as unread.
  • unread โ€” ๐Ÿ“ญ Takes a message list and marks each message as not having been read.
  • unretain โ€” ๐Ÿฅ€ Superseded by the multiplexer headerpick.
  • unsaveignore โ€” [Obsolete] ๐Ÿฅ€ Superseded by the multiplexer headerpick.
  • unsaveretain โ€” [Obsolete] ๐Ÿฅ€ Superseded by the multiplexer headerpick.
  • unset โ€” [Only new quoting rules] (uns) ๐Ÿซฅ See set.
  • unshortcut โ€” ๐Ÿซฅ See shortcut.
  • unsort โ€” ๐Ÿซฅ See short.
  • unthread โ€” [Obsolete] ๐Ÿฅ€ Same as unsort.
  • urlcodec โ€” ๐ŸŒ Performs URL percent codec operations on the raw-data argument, going by RFC 3986 ๐Ÿ“œ. The first argument picks the operation: e[ncode] or d[ecode] do plain URL percent en- and decoding, respectively. p[ath]enc[ode] and p[ath]dec[ode] do a slightly modified version that's better for pathnames. It doesn't allow a tilde ~, and it won't accept a hyphen-minus - or a dot . as an initial character. The rest of the line is the URL data to convert. This is character set agnostic, so it may decode bytes that are invalid in the current ttycharset. โš ๏ธ [v15 behaviour may differ] This command doesn't know about URLs beyond what's documented here. (vexpr offers a makeprint subcommand, should you want the URL displayed.)

Supports vput (see โ€œCommand modifiersโ€), and manages the error number !. If the coding operation fails, the error number ! is set to ^ERR-CANCELED, and the unmodified input is used as the result. The error number may change again due to output or result storage errors.

  • varshow โ€” [Only new quoting rules] ๐Ÿ‘€ Produces the same output as the listing mode of set, including verbosity adjustments, but only for the given variables.
  • verify โ€” [Option] ๐Ÿ” Takes a message list and verifies each message. If a message isn't an S/MIME signed message, verification will fail for it. The check looks at whether the message was signed using a valid certificate, whether the message sender's email address matches one of those contained within the certificate, and whether the message content has been altered.
  • version โ€” ๐Ÿท๏ธ Shows the version and features of S-nail, optionally in a more verbose form that also includes the build and running system environment. Supports vput (see โ€œCommand modifiersโ€).
  • vexpr โ€” [Only new quoting rules] ๐Ÿงฎ A multiplexer command offering signed 64-bit numeric calculations, plus other mostly string-based operations. C-style byte string operations are available via csop. The first argument defines the number, type, and meaning of the remaining arguments. An empty number argument is treated as 0. Supports vput (see โ€œCommand modifiersโ€). The result shown in case of errors is -1 for usage errors and numeric operations, and the empty string otherwise. โ€œSoftโ€ errors, like when a search operation failed, will also set the ! error number to ^ERR-NODATA. Except when otherwise noted, numeric arguments are parsed as signed 64-bit numbers, and errors will be reported in the error number ! as the numeric error ^ERR-RANGE.

Numeric operations work on one or two signed 64-bit integers. Numbers prefixed with 0x or 0X are read as hexadecimal (base 16), whereas 0 indicates octal (base 8), and 0b as well as 0B denote binary (base 2) numbers. It's possible to use any base in between 2 and 36, inclusive, with the BASE#number notation, where the base is given as an unsigned decimal number, so 16#AFFE is a different way of specifying a hexadecimal number. Unsigned interpretation of a number can be enforced by prefixing a u (case-insensitively), as in u-110. This isn't necessary for power-of-two bases (2, 4, 8, 16 and 32), which will be interpreted as unsigned by default, but it still makes a difference regarding overflow detection and overflow constant. It's possible to enforce signed interpretation by prefixing an s instead (case-insensitively). The number sign notation uses a permissive parse mode, and as such supports complicated conditions out of the box ๐Ÿช„:

? wysh set ifs=:;read i;unset ifs;echo $i;vexpr pb 2 10#$i
   -009
<   -009>
0b1001

One integer is expected by assignment (equals sign =), which does nothing but parse the argument, thus detecting validity and possible overflow conditions. Unary not (tilde ~) creates the bitwise complement, and unary plus and minus are there too. Two integers are used by addition (plus sign +), subtraction (hyphen-minus -), multiplication (asterisk *), division (solidus /) and modulo (percent sign %), as well as for the bitwise operators logical or (vertical bar |, to be quoted), bitwise and (ampersand &, to be quoted), bitwise xor (circumflex ^), the bitwise signed left and right shifts (<<, >>), as well as the unsigned right shift >>>. Math bestie, we did it ๐ŸŒˆ๐Ÿ‘‘

Okay so pbase is next, and it's lowkey my favourite. ๐Ÿงš It takes a number base from 2 to 36 (both ends count, bestie!) and then works on the second number exactly like = does. The difference is the result comes out in whatever base you picked, as a signed 64-bit number, unless you forced unsigned interpretation of the input with a u prefix. ๐Ÿ”ฅ

Numeric operations also have a saturated mode ๐Ÿ’…, unlocked with the ? modifier suffix. The word saturated is optional, so +?, +?satu, and +?saturated are literally the same thing. In saturated mode, overflow errors and division or modulo by zero stop showing up in the exit status. Instead, the result just clings to the minimum or maximum possible value rather than overflowing (or trapping) ๐Ÿซถ This also covers the argument parse step. For the bitwise shifts, the saturated maximum is 63. Any caught overflow gets reported through the error number ! as ^ERR-OVERFLOW.

? vput vexpr res -? +1 -9223372036854775808
? echo $?/$!/$^ERRNAME:$res
0/75/OVERFLOW:-9223372036854775808

The character-set-agnostic string functions don't care about locale settings or character sets at all. ๐ŸŒˆ

  • date-utc โ€” Outputs the current date and time in UTC (Coordinated Universal Time), with values named so that vput vexpr x date-utc; eval wysh set $x creates accessible variables. โฐ
  • date-stamp-utc โ€” Outputs an RFC 3339 internet date/time format of UTC. ๐Ÿ“…
  • epoch โ€” The seconds and nanoseconds since the Unix epoch (1970-01-01T00:00:00), named epoch_sec and epoch_nsec, so vput vexpr x epoch; eval wysh set $x creates accessible variables. ๐Ÿฆ‹
  • file-expand โ€” Runs the usual "Filename transformations" on its argument. ๐Ÿ—‚๏ธ
  • file-stat, file-lstat โ€” These run the usual "Filename transformations" on the argument, then call stat(2) and lstat(2) respectively, and spit out values so that vput vexpr x file-stat FILE; eval wysh set $x creates accessible variables. The st_type variable is where it gets fun ๐Ÿ‘‘ It uses:
    • / for directories
    • @ for links
    • # for block devices
    • % for character devices
    • | for FIFOs
    • = for sockets
    • . for the rest
  • random โ€” Generates a random string of the given length, or of PATH_MAX bytes (a constant from /usr/include) if the value 0 is given. The string is base64url encoded according to RFC 4648, so it's a (portable) filename you can actually use. ๐ŸŒธ

String operations work, with sufficient support provided, according to the active user's locale encoding and character set (see "Character sets"). Where the ? modifier suffix is supported, there's a case-insensitive mode too ๐ŸŽ€ The keyword case is optional, so regex? and regex?case are identical.

  • makeprint (One-way) โ€” Converts the argument into something safely printable on the terminal. ๐Ÿ™ˆ
  • regex [Option] โ€” A string operation that tries to match the first argument against the regular expression given as the second argument. The ? modifier suffix works here too. If you give an optional third argument, you get a replacement operation instead of the match offset. That third argument is treated as if it were written inside dollar-single-quote (see "Shell-style argument quoting"), and any positional parameter like 0, 1, etc. gets swapped out for the matching group of the regular expression โœจ
? vput vexpr res regex bananarama \
    (.*)NanA(.*) '\${1}au\$2'
? echo $?/$!/$^ERRNAME:$res:
1/61/NODATA::
? vput vexpr res regex?case bananarama \
    (.*)NanA(.*) '\${1}uauf\$2'
? echo $?/$!/$^ERRNAME:$res:
0/0/NONE:bauauframa:

๐Ÿงš vpospar [Only new quoting rules]

Manage the positional parameter stack (see 1, #, *, @ as well as shift). With clear, the positional parameter stack of the current context gets wiped, or the global one if there isn't a current context. ๐Ÿงน

With set, the remaining arguments are used to (re)create the stack. If the parameter stack size limit gets exceeded, an ^ERR-OVERFLOW error occurs. ๐Ÿšจ

With quote, you get a round-trip capable representation of the stack contents. Each quoted parameter is separated from the next by the first character of ifs, and followed by the first character of if-ws, as long as that isn't empty and isn't identical to the first. If that ends up with no separation at all, a space character is used. This mode supports vput (see "Command modifiers"). In other words, the subcommands set and quote can be used together with eval to (re)create an argument stack from and to a single variable, losslessly ๐Ÿ’–

? vpospar set hey, "'you    ", world!
? echo $#: <${1}><${2}><${3}>
? vput vpospar x quote
? vpospar clear
? echo $#: <${1}><${2}><${3}>
? eval vpospar set ${x}
? echo $#: <${1}><${2}><${3}>

๐Ÿฆ‹ visual (v)

Takes a message list and invokes the VISUAL display editor on each message. Any modified contents get discarded unless the writebackedited variable is set. They're only used if the mailbox can be written to and the editor returns a successful exit status. If you want something less display oriented, edit can be used instead.

๐Ÿ’… write (w)

For conventional messages, the body gets written without all the headers. The original message is never marked for deletion in the originating mail folder. The output is decrypted and converted to its native format as necessary. If the output file already exists, the text gets appended. If a message is in MIME multipart format, its first part is written to the specified file just like a conventional message, and what happens to the remaining parts depends on the execution mode. No special handling of compressed files is performed.

In interactive mode, you're consecutively asked for the filenames of the processed parts. For convenience, you can skip saving any part by giving an empty value, which has the same result as writing it to /dev/null. Shell piping the part content is also supported, by giving a leading vertical bar | character for the filename. Other user input goes through the usual "Filename transformations", including shell pathname wildcard pattern expansions (glob(7)) and shell variable expansion. That's for the message as such, not the individual parts. Also, the contents of the destination file get overwritten if the file previously existed. Character set conversion to ttycharset is performed when saving text data. ๐Ÿ’Œ

๐Ÿ’€ [v15 behaviour may differ] In non-interactive mode, any part that doesn't specify a filename is ignored. Suspicious parts of the filenames of the remaining parts are URL percent encoded (as via urlcodec) to prevent injection of malicious character sequences, which results in a filename that gets written into the current directory. Existing files will not be overwritten. Instead, the part number or a dot is appended after a number sign # to the name until file creation succeeds (or fails due to other reasons).

๐ŸŒˆ xcall [Only new quoting rules]

The only difference from call is that the new macro runs in place of the current one, and the current one won't regain control. All resources of the current macro get released first. This means any setting covered by localopts gets forgotten, and covered variables become cleaned up. If this command isn't used from within a called macro, it silently becomes a (more expensive variant of) call. ๐Ÿงผ

๐Ÿ“ xit (x)

A synonym for exit. ๐Ÿ‘‹

๐Ÿซถ z [Only new quoting rules]

S-nail presents message headers in screenfuls, just like the headers command does. Without arguments, this command scrolls to the next window of messages, and so does an argument of +. An argument of - scrolls to the last one, ^ scrolls to the first, and $ scrolls to the last screen of messages. A number argument prefixed with + or - is calculated relative to the current position, while a number without a prefix specifies an absolute position. ๐Ÿ“œ

๐Ÿ’œ Z [Only new quoting rules]

Similar to z, but this one scrolls to the next or previous window that contains at least one 'new' or flagged message. Main character energy ๐Ÿ‘‘


โœจ COMMAND ESCAPES โœจ

Command escapes are available in "Compose mode" during interactive usage, when explicitly requested via -~, and in batch mode (-#). They do special things like editing the headers of the message being composed, calling normal "COMMANDS", yielding a shell, and so on ๐ŸŒธ Command escapes are only recognized at the beginning of lines, and they consist of an escape followed by a command character. The default escape character is the tilde ~. Iykyk, and now you do! ๐Ÿฆ‹

Okay so, unless a command escape says otherwise, escapes keep the error number ! and the exit status ? up to date ๐Ÿ’… The variable errexit decides whether a failed operation errors out, message compose mode, and causes the program to exit ๐Ÿซถ You can prefix an escape with nothing, or with several single-character command modifiers, and any whitespace in between gets ignored โœจ

  • - โ€” Hyphen-minus gives you the same effect as the command modifier ignerr, and it overrides errexit ๐Ÿ”ฅ
  • $ โ€” Dollar evaluates the rest of the line. Check out โ€œShell-style argument quotingโ€ for more on that ๐Ÿงš [v15 behaviour may differ] For now the entire input line gets evaluated as a whole, so control operators like semicolon ; can get interpreted when you didn't mean them to. Quote them to avoid that ๐Ÿ’€

Adding the command line to the [Option]al history can be prevented by putting whitespace directly after the escape ๐Ÿ™ˆ The [Option]al key bindings support a compose mode specific context. These are the command escapes that are supported:

  • ~~ string โ€” Inserts the string into the message, prefaced by a single ~ ๐Ÿ’– If the escape character has been changed, that character has to be doubled instead.
  • ~! command โ€” Runs the shell command that follows ๐Ÿš Unescaped exclamation marks get replaced with the previously executed command if the internal variable bang is set, and then you're back in the message.
  • ~. โ€” Ends compose mode and sends the message ๐Ÿš€ Here's the order of things, bestie:
    • The hooks on-compose-splice-shell and on-compose-splice get called first, in that order, when they're set.
    • In interactive mode, askatend (which leads to askcc and askbcc) gets checked, and askattach gets checked as well, plus asksend.
    • A set on-compose-leave hook gets called next.
    • autocc and autobcc get joined in if they're set.
    • A given message-inject-tail gets incorporated.
    • Then compose mode is left, and that's the end of it ๐Ÿฆ‹
  • ~: S-nail-command or ~_ S-nail-command โ€” Lets you run โ€œCOMMANDSโ€ from inside compose mode, since those are allowed here too ๐Ÿ‘‘
  • ~< filename โ€” Identical to ~r ๐ŸŒธ
  • ~<! command โ€” The command gets executed using the shell, and its standard output is inserted into the message ๐Ÿ“
  • ~? โ€” [Option] Writes a summary of the command escapes ๐Ÿ“
  • ~@ [filename...] โ€” Appends to or edits the list of attachments ๐Ÿ“Ž It does not manage the error number ! and the exit status ?, so use ~^ if you need proper error handling ๐Ÿ’…
    • In append mode, you give a list of filename arguments as shell tokens (see โ€œShell-style argument quotingโ€, and token-separating commas are ignored too). They get interpreted the same way as documented for the command line option -a, with the message number exception below.
    • Without filename arguments, the attachment list gets edited entry by entry. If a filename is left empty, that attachment is deleted from the list. Once you hit the end of the list, you can either enter new attachments or quit by committing an empty โ€œnewโ€ attachment โœจ
    • In non-interactive mode or in batch mode (-#), the list of attachments isn't really edited. It gets recreated instead, and again an empty input ends list creation.
    • In every mode, if a filename consists solely of the number sign # followed by either a valid message number of the currently active mailbox, or a period . (referring to the current message of the active mailbox, aka the so-called โ€œdotโ€), then that message gets attached as a message/rfc822 MIME message part ๐Ÿฆ‹ The number sign must be quoted so it doesn't get mistaken for a shell comment character ๐Ÿ™ˆ
  • ~| command โ€” Pipes the message text through the filter command you specify ๐ŸŒˆ If the command gives no output or terminates abnormally, the original text of the message is kept. The command fmt(1) is often used as a rejustifying filter ๐ŸŽ€
    • If the first character of the command is a vertical bar |, then the entire message, including the header fields, is subject to the filter command. So ~|| echo Fcc: /tmp/test; cat will prepend a file-carbon-copy message header ๐Ÿ˜ญ Also see ~e and ~v.
  • ~^ cmd [subcmd [arg3 [arg4]]] โ€” Inspects and modifies the message using the semantics of digmsg, so the arguments get evaluated according to โ€œShell-style argument quotingโ€ ๐Ÿ” Error number ! and exit status ? aren't managed here. Errors get handled via the protocol instead, and hard errors like I/O failures can't be handled at all ๐Ÿ’€
Heads up: the protocol is made up of command lines, each followed by response line(s). The first field of a response line is a status code. It tells you whether a command was successful, whether result data is to be expected, and if so, what format that data is in. Response data gets shell quoted as necessary so it can be consumed by readsh, or by eval and vpospar, to name a few. Error status code lines may optionally contain additional context.

The status codes you can get back are:

  • 210 โ€” Status ok. The remains of the line are the result โœจ
  • 211 โ€” Status ok. The rest of the line is optionally used for more status. What follows are lines of result addresses, terminated by an empty line ๐Ÿ“ฌ All the input, including that empty line, must be consumed before further commands can be issued. Address lines consist of two tokens. The first is the plain network address, e.g., bob@exam.ple, followed by the (quoted) full address as known: '(Lovely) Bob <bob@exam.ple>' ๐Ÿ’– Non-network addresses use the first field to indicate their type instead (hyphen-minus - for files, vertical bar | for pipes, and number sign # for names which will undergo alias processing), and the actual value goes in the second field.
  • 212 โ€” Status ok. The rest of the line is optionally used for more status. What follows are lines of furtherly unspecified (quoted) string content, terminated by an empty line. All the input, including the empty line, must be consumed before further commands can be issued ๐Ÿงพ
  • 500 โ€” Syntax error; invalid command ๐Ÿšซ
  • 501 โ€” Syntax error, or otherwise invalid parameters or arguments ๐Ÿ™…
  • 505 โ€” Error: an argument fails verification. For example, an invalid address has been specified (also see expandaddr), or someone tried to modify anything in S-nail's own namespace, or a modifying subcommand got used on a read-only message ๐Ÿ”’
  • 506 โ€” Error: an otherwise valid argument gets rendered invalid due to context. For example, a second address gets added to a header which may consist of a single address only ๐Ÿ˜ค

If a command indicates failure, the message will have stayed unmodified ๐Ÿ›ก๏ธ Most commands can fail with 500 if required arguments are missing, or if excessive arguments were given (false command usage). [v15 behaviour may differ] The latter doesn't happen regularly yet, because, as stated in โ€œShell-style argument quotingโ€, our argument parser isn't smart enough to work on a subcommand basis ๐Ÿค“ For example, you might get an excess argument error for a three-argument subcommand that receives four arguments, but not for a four-argument subcommand that receives six arguments. Here the excess gets joined. The following (case-insensitive) commands are supported:

  • attachment โ€” Lets you list, remove, and add message attachments ๐Ÿ“Ž The second argument specifies the subcommand to apply, and it's one of these:
    • attribute โ€” Uses the same search mechanism described for remove, and prints any known attributes of the first found attachment via 212 upon success, or 501 if no such attachment can be found. The attributes are written as lines with a keyword and a value token ๐Ÿท๏ธ
    • attribute-at โ€” Uses the same search mechanism described for remove-at, and is otherwise identical to attribute ๐ŸŽฏ
    • attribute-set โ€” Uses the same search mechanism described for remove, and sets the attribute given as the fourth token to the value given as the fifth token argument โœ๏ธ If the value is an empty token, the given attribute is removed, or reset to a default value if the attribute's existence is crucial.
      • It returns via 210 upon success, with the index of the found attachment following. It returns 505 for message attachments or if the given keyword is invalid, and 501 if no such attachment can be found ๐Ÿ’…
      • The following keywords may be used (case-insensitively):
  • filename ๐Ÿ“Ž: sets the filename of the MIME part, which is the name that shows up for display and gets suggested when someone saves it ๐Ÿ’พ
  • content-description โœจ: attaches some descriptive info to the attachment's content. Some MUAs prefer this over the plain filename, so it's lowkey a glow-up ๐Ÿ’…
  • content-id ๐Ÿ†”: can be used to uniquely identify MIME entities in several contexts. It expects a special reference address format defined in RFC 2045, and if the address content fails verification it generates 505 ๐Ÿ™ˆ
  • content-type ๐ŸŽ€: defines the media type/subtype of the part. It's managed automatically, but you're allowed to overwrite it ๐Ÿ’–
  • content-disposition ๐Ÿฆ‹: automatically set to the string attachment

attribute-set-at ๐Ÿ”

This uses the same search mechanism described for remove-at, and is otherwise identical to attribute-set ๐Ÿ’œ.

attachment: insert ๐Ÿ“ฅ

Adds the attachment given as the third argument. It's specified exactly like the command line option -a, and it supports the message number extension documented for ~@ ๐Ÿงš. It reports 210 on success, followed by the index of the new attachment. It reports 505 if the file can't be opened, and 506 if an on-the-fly character set conversion fails. Otherwise it reports 501, which also happens if character set conversion is requested but isn't available ๐Ÿ˜ญ.

attachment: list ๐Ÿ“‹

Lists all attachments via 212, or reports 501 if there are no attachments at all. This is the default command of attachment if no second argument is given ๐Ÿซถ.

attachment: remove ๐Ÿ—‘๏ธ

Removes the attachment given as the third argument, and reports 210 on success or 501 if no such attachment can be found ๐Ÿ’€. If there's any path component in the argument, an exact match of the path used to create the attachment is used directly. But if only the basename matches, all attachments get traversed to find an exact match first, and then the removal happens ๐Ÿ”ฅ. If multiple basenames match, you get a 506 error. Message attachments are treated as absolute pathnames ๐Ÿ‘‘.

If there's no path component in the argument, then all attachments are searched for filename= parameter matches, as well as matches of the basename of the path that was used when the attachment was created. Multiple matches result in a 506 ๐Ÿ™…โ€โ™€๏ธ.

attachment: remove-at ๐ŸŽฏ

This interprets the third argument as a number and removes the attachment at that list position (counting from one!) ๐Ÿ”ข. It reports 210 on success, 505 if the argument isn't a number, or 501 if no such attachment exists ๐Ÿ’….

This command lets you list, inspect, and edit message headers ๐ŸŽ€. Header name case is not normalized, so you should use case-insensitive comparison when matching names ๐Ÿ™ˆ. The second argument picks the subcommand, and it's one of the following:

header: insert โœ๏ธ

Creates a new instance (or an additional one) of the header given in the third argument, with the header body content given in the fourth token ๐Ÿ’–. It may return 501 if the third argument is a free-form header field name that's invalid, or if extracting the body content fails. It returns 505 if any extracted address fails syntax and/or security checks, or on S-nail namespace violations. It returns 506 to prevent you from exceeding a single-instance header ๐Ÿšซ. Heads up: Subject: can be appended to, and a space separator gets added automatically first โœจ.

To:, Cc: and Bcc: support the ?single modifier, which enforces treatment as a single addressee. For example: header insert To?single: 'exa, <m@ple>'. The word single is optional ๐Ÿงš.

210 is returned on success, followed by the name of the header and the list position of the newly inserted instance. The list position is always 1 for single-instance header fields. All free-form header fields are managed in a single list, so also see customhdr ๐Ÿ“œ.

header: list ๐Ÿ“‹

Without a third argument, you get a list of all existing headers via 210. This is the default command of header if no second argument is given. A third argument restricts the output to just the given header, and that may fail with 501 if no such field is defined ๐Ÿซถ.

header: remove ๐Ÿ—‘๏ธ

Removes all instances of the header given as the third argument. It reports 210 on success, 501 if no such header can be found, and 505 on S-nail namespace violations ๐Ÿ’€.

header: remove-at ๐ŸŽฏ

Removes, from the header given as the third argument, the instance at the list position (counting from one!) given with the fourth argument ๐Ÿ”ข. It reports 210 on success. It reports 505 if the list position argument isn't a number or on S-nail namespace violations, and 501 if no such header instance exists ๐Ÿ’….

header: show ๐Ÿ‘€

Shows the content of the header given as the third argument. Depending on the header type, this may respond with 211 or 212. Any failure results in 501 ๐Ÿ˜ญ.

In compose-mode, you get read-only access to optional pseudo headers in the S-nail private namespace ๐Ÿ”ฎ:

  • Mailx-Command: ๐Ÿงพ: the name of the command that generates the message. It's one of forward, Lreply, mail, Reply, reply, or resend. This pseudo header always exists in compose-mode ๐Ÿ’œ.
  • Mailx-Raw-To:, Mailx-Raw-Cc:, Mailx-Raw-Bcc: ๐ŸงŠ: these represent the frozen initial state of these headers, before any transformation (alias, alternates, recipients-in-cc etc.) took place ๐Ÿฆ‹.
  • Mailx-Orig-Sender:, Mailx-Orig-From:, Mailx-Orig-To:, Mailx-Orig-Cc:, Mailx-Orig-Bcc: ๐Ÿ“จ: the values of those headers from the original message that got addressed by any of reply, forward, or resend. The sender field is special: it's filled in with the sole sender according to RFC 5322 rules, so it may be equal to the from field ๐Ÿค“.

help, ? ๐Ÿ†˜

Shows an abstract of the above commands via 211 ๐Ÿ’–.

version ๐Ÿ”–

Prints the protocol version via 210 โœจ.


~A ๐Ÿ–Š๏ธ

The same as ~i Sign ๐Ÿ’….

~a ๐Ÿ–‹๏ธ

The same as ~i sign ๐Ÿ’….

~b name ... ๐Ÿ™ˆ

Adds the given names to the list of blind carbon copy recipients ๐Ÿซฃ.

~c name ... ๐Ÿ“ฎ

Adds the given names to the list of carbon copy recipients ๐Ÿ’Œ.

~d ๐Ÿ“‚

Reads the file specified by the DEAD variable into the message ๐Ÿงš.

  • ~e โ€” Okay bestie, this one pops open your EDITOR on whatever you've written so far, then drops you right back into compose mode ๐Ÿ’… ~v is the fancier, more display-oriented option, and ~|| is the pipe-based vibe ๐Ÿฆ‹
  • ~F messages โ€” This grabs the named messages and drops them into your draft, headers and MIME parts included โœจ It honours forward-add-cc, forward-inject-head and forward-inject-tail. No messages named? It grabs the current message, the โ€œdotโ€ ๐Ÿ’–
  • ~f messages โ€” Same idea, but way more curated ๐ŸŽ€ It trims the header fields using the โ€˜forwardโ€™ white- and blacklist selection of headerpick (or โ€˜typeโ€™ when posix is on). It also honours forward-add-cc, forward-inject-head and forward-inject-tail. For MIME multipart messages, only the first displayable part comes along ๐Ÿงš No messages named? Dot again.
  • ~H โ€” In interactive mode, you get to edit From:, Reply-To: and Sender: one at a time ๐ŸŒธ The starting values come from the from, reply-to and sender variables. In non-interactive mode, this sets ^ERR-NOTTY, so, not it ๐Ÿ™ˆ
  • ~h โ€” Interactive mode only: edit To:, Cc:, Bcc: and Subject: one by one ๐Ÿ“ Non-interactive mode sets ^ERR-NOTTY, same as before ๐Ÿ’€
  • ~I variable โ€” Pops the value of the variable you name right into your message ๐ŸŒˆ If the variable is unset or empty, your message stays exactly how it was. In posix mode, the sequences \t (horizontal tab) and \n (line feed) get expanded. Otherwise the expansion happens at set time ([v15 behaviour may differ] by using the command modifier wysh) ๐Ÿซถ
  • ~i variable โ€” Literally the same as ~I, except it tacks a newline on the end โœจ
  • ~M messages โ€” Reads the named messages in, indented by indentprefix ๐Ÿ’œ No messages? Current message, the โ€œdotโ€. It honours forward-add-cc, forward-inject-head and forward-inject-tail ๐Ÿฆ‹
  • ~m messages โ€” Same indented energy as ~M, but this one strips the header fields using the โ€˜typeโ€™ white- and blacklist selection of headerpick ๐ŸŒธ It also honours forward-add-cc, forward-inject-head and forward-inject-tail. For MIME multipart messages, only the first displayable part is included ๐Ÿ”ฅ No messages named? Dot.
  • ~p โ€” Shows you the message collected so far, with the header fields at the top and the attachment list at the bottom if there are any ๐Ÿ‘‘
  • ~Q โ€” Reads in the given (or current) message(s) using the quote algorithm ๐Ÿ’… Quote is implicitly assumed here even if it isn't set. It honours quote-add-cc ๐ŸŽ€
  • ~q โ€” Aborts the message you're writing ๐Ÿ’€ If save is set, it copies the message to the file the DEAD variable points at ๐Ÿ˜ญ
  • ~R filename โ€” Identical to ~r, but every line that gets read in is indented by indentprefix ๐Ÿงš
  • ~r filename [HERE-delimiter] โ€” Reads the named file into your message ๐Ÿซถ This is subject to โ€œFilename transformationsโ€, but shell globs and variable expansions are excluded. If filename is the hyphen-minus -, standard input gets used instead, which is great for pasting โœจ HERE-delimiter can only be given in this stdin mode. If you use one, it'll keep reading until that delimiter shows up alone on a line. Hitting EOF before that is an error ๐Ÿ™ˆ The HERE-delimiter is a required argument in non-interactive mode. If it's single-quote quoted, the pasted content won't be expanded. [v15 behaviour may differ] otherwise, a future version of S-nail may perform shell-style expansion on the content ๐Ÿ‘€
  • ~s string โ€” Makes the named string your current subject field ๐Ÿ’– Heads up: newline (NL) and carriage-return (CR) bytes are invalid, and they'll be normalized to space (SP) characters โš ๏ธ
  • ~t name ... โ€” Adds the given name(s) to your direct recipient list ๐Ÿ’œ
  • ~U messages โ€” Reads in the given (or current) message(s) without any headers, indented by indentprefix ๐ŸŒˆ It honours forward-add-cc, forward-inject-head and forward-inject-tail ๐Ÿ’…
  • ~u messages โ€” Same as ~U, but without the indentation ๐Ÿ“ Reads in the given (or current) message(s), excluding all headers. It honours forward-add-cc, forward-inject-head and forward-inject-tail ๐Ÿซถ
  • ~v โ€” Invokes the VISUAL editor on your message so far, then returns you to compose mode ๐Ÿฆ‹ ~e is the less display-oriented choice, and ~|| is the pipe-based approach ๐ŸŒธ
  • ~w filename โ€” Writes the message onto the named file, subject to the usual โ€œFilename transformationsโ€ ๐ŸŽ€ If the file already exists, the message gets appended to it ๐Ÿงš
  • ~x โ€” Same as ~q, except the message isn't saved at all, not even a little ๐Ÿ’€

โœจ INTERNAL VARIABLES โœจ

Internal S-nail variables are controlled with the set and unset commands ๐ŸŽ€ Prefix a variable name with โ€˜noโ€™ and call set, and it does the same thing as unset. So unset crt and set nocrt are identical ๐Ÿ’… varshow gives you more insight on the variable(s) you name. If you call set with no arguments, it shows a listing of all variables. Both commands support a more verbose listing mode ๐Ÿ”ฅ Some well-known variables get inherited from the program โ€œENVIRONMENTโ€ implicitly. Others can be imported explicitly with the command environ, and then they share those same properties โœจ

There are two kinds of internal variables, and both can form chains ๐ŸŒˆ Boolean variables can only be in one of two states, โ€œsetโ€ or โ€œunsetโ€. Value variables hold an (optional) string value. For value variables, proper quoting is necessary at assignment time. The introduction of the section โ€œCOMMANDSโ€ documents the supported quoting rules ๐Ÿซถ

Here's a quick example of assignment and quoting, bestie ๐Ÿงš
? wysh set one=val\ 1 two="val 2" \
    three='val "3"' four=$'val \'4\''; \
    varshow one two three four; \
    unset one two three four

Depending on the actual option string, its value might get interpreted as colour names, command specifications, normal text, and more ๐ŸŽ€ Values can be treated as numbers. If a value is documented as decimal, decimal is what's expected. Otherwise, any numeric format and base that the vexpr command understands is fair game too ๐Ÿ”ข

There's also a special kind of string value called the โ€œboolean stringโ€ ๐Ÿ’– It must either be a decimal integer (where โ€˜0โ€™ is false, and โ€˜1โ€™ and any other value are true), or one of these case-insensitive strings. โ€˜offโ€™, โ€˜noโ€™, โ€˜nโ€™ and โ€˜falseโ€™ mean false. โ€˜onโ€™, โ€˜yesโ€™, โ€˜yโ€™ and โ€˜trueโ€™ mean true ๐Ÿ’… A special kind of boolean string is the โ€œquadoptionโ€. It can optionally be prefixed with the case-insensitive term โ€˜ask-โ€™, as in โ€˜ask-yesโ€™. In interactive mode the user gets prompted. Otherwise the actual boolean is used ๐Ÿ‘‘

Variable chains extend a plain โ€˜variableโ€™ with โ€˜variable-HOSTโ€™ and โ€˜variable-USER@HOSTโ€™ variants ๐Ÿฆ‹ โ€˜HOSTโ€™ gets converted to all lowercase when it's looked up (but not when the variable is set or unset!). It may also be [Option]ally IDNA converted. It means โ€˜server:portโ€™ if a โ€˜portโ€™ was specified in the contextual Uniform Resource Locator URL. See โ€œOn URL syntax and credential lookupโ€ ๐ŸŒธ Even though this mechanism is based on URLs, no URL percent encoding may be applied to either โ€˜USERโ€™ or โ€˜HOSTโ€™. Variable chains need raw data, and the mentioned section has examples ๐Ÿ“ Variables that support chains are explicitly documented as such. S-nail treats the base name of any such variable as special, so please don't create custom names like โ€˜variable-xyzโ€™ to avoid false classifications and treatment of those variables ๐Ÿ™ˆ

๐Ÿ’– Initial settings ๐Ÿ’–

The standard POSIX 2008/Cor 2-2016 mandates the following initial variable settings: noallnet, noappend, asksub, noaskbcc, noautoprint, nobang, nocmd, nocrt, nodebug, nodot, escape set to โ€˜~โ€™, noflipr, nofolder, header, nohold, noignore, noignoreeof, nokeep, nokeepsave, nometoo, nooutfolder, nopage, prompt set to โ€˜? โ€™, noquiet, norecord, save, nosendwait, noshowto, noSign, nosign, toplines set to โ€˜5โ€™ ๐ŸŽ€

However, S-nail has built in some initial (and some default) settings which (may) diverge from that list ๐ŸŒˆ Others can be adjusted by one of the โ€œResource filesโ€. To display the former, use set: $ s-nail -:/ -v -Xset -Xx ๐Ÿ’… In general, this implementation sets (and has extended the meaning of) sendwait, and it does not support the noonehop variable. Use command line options or mta-arguments to pass options through to an mta โœจ The default global resource file sets, among others, the variables hold, keep and keepsave, and establishes a default headerpick selection etc. So you should take those into account ๐Ÿซถ

๐ŸŽ€ Variables ๐ŸŽ€

  • ? โ€” (Read-only) This is the exit status of the last command, or the return value of the macro called last ๐Ÿงš It has a meaning in the state machine. In conjunction with errexit, any non-0 exit status causes a program exit. In posix mode, any error while loading (any of the) resource files has the same effect ๐Ÿ”ฅ ignerr, one of the โ€œCommand modifiersโ€, can be used to tell the state machine to ignore errors ๐Ÿ™ˆ
  • ! โ€” (Read-only) This is the current error number (errno(3)), which gets set after an error happens ๐Ÿ’€ It's also available via ^ERR, and you can query the error name and documentation string with ^ERRNAME and ^ERRDOC. [v15 behaviour may differ] This machinery is new, so the error number is only really usable if a command explicitly states that it manages the variable !. For others, errno gets used in case of errors, or ^ERR-INVAL if that is 0. So it may or may not reflect the real error ๐Ÿ˜ญ The error number may be set with the command return ๐Ÿ’œ
  • ^: ๐Ÿ”ฎ (Read-only) Okay, this is a multiplexer variable, and it's giving dynamic. It expands into whatever state or condition you ask for. The options are:
    • ^ERR, ^ERRDOC, ^ERRNAME: The number, documentation, and name of the current errno(3), respectively, which is usually set after an error occurred. The documentation is an [Option], and the name gets used if no documentation is available. [v15 behaviour may differ] This machinery is brand new and is usually reliable only if a command explicitly says it manages the variable !, which is effectively identical to ^ERR. Each of those can be suffixed with a hyphen-minus followed by a name or number, and then the expansion refers to that given error. It's a direct mapping of (a subset of) the system error values, so here's a little demo ๐Ÿ’…:
define work {
  eval echo \$1: \$^ERR-$1:\
    \$^ERRNAME-$1: \$^ERRDOC-$1
  vput vexpr i + "$1" 1
  if [ $i -lt 16 ]
    \xcall work $i
  end
}
call work 0

^ERRQUEUE-COUNT, ^ERRQUEUE-EXISTS: ๐Ÿงš The number of messages in the [Option]al queue of errors, plus a string showing the queue state: either empty or (translated) โ€œERRORโ€. These are always 0 and the empty string, respectively, unless features includes ,+errors,.

  • *: ๐ŸŽ€ (Read-only) Expands all positional parameters (see 1), separated by the first character of the value of ifs. [v15 behaviour may differ] The special semantics of the equally named special parameter of the sh(1) are not supported yet, so don't count on those.
  • @: ๐ŸŒธ (Read-only) Expands all positional parameters (see 1), separated by a space character. If you put it in double quotation marks, each positional parameter gets properly quoted so it expands back to a single parameter again. Iconic behaviour, honestly.
  • #: ๐Ÿ”ข (Read-only) Expands to the number of positional parameters, which is the size of the positional parameter stack in decimal.
  • 0: ๐Ÿฆ‹ (Read-only) Inside the scope of a defined and called macro, this expands to the name of the calling macro, or to the empty string if the macro is running from top-level. For the [Option]al regular expression search and replace operator of vexpr, this expands to the entire matching expression. It represents the program name in global context.
  • 1: ๐Ÿ“š (Read-only) This is access to the positional parameter stack. All further parameters can be reached with this syntax too: 2, 3, and so on. Positional parameters can be shifted off the stack by calling shift. The stack holds, for example, the arguments of a called defined macro and the matching groups of the [Option]al regular expression search and replace expression of vexpr, and it can be explicitly created or overwritten with the command vpospar.
  • account: ๐Ÿ’ผ (Read-only) Is set to the active account.
  • add-file-recipients: ๐Ÿ“Ž (Boolean) When file or pipe recipients have been specified, this mentions them in the corresponding address fields of the message instead of quietly stripping them from the recipient list. By default such addressees are not mentioned.
  • allnet: ๐ŸŒ (Boolean) Causes only the local part to be evaluated when comparing addresses.
  • append: โž• (Boolean) Causes messages saved in the โ€œsecondary mailboxโ€ MBOX to be appended to the end rather than prepended. This should always be set, bestie.
  • askatend: โณ (Boolean) Causes the prompts for โ€˜Cc:โ€™ and โ€˜Bcc:โ€™ lists to show up after the message has been edited.
  • askattach: ๐Ÿ“‚ (Boolean) If set, S-nail asks an interactive user for files to attach at the end of each message. An empty line finalizes the list.
  • askcc: ๐Ÿ’Œ (Boolean) Causes the interactive user to be prompted for carbon copy recipients, at the end of each message if askatend or bsdcompat are set.
  • askbcc: ๐Ÿ™ˆ (Boolean) Causes the interactive user to be prompted for blind carbon copy recipients, at the end of each message if askatend or bsdcompat are set.
  • asksend: ๐Ÿš€ (Boolean) Causes the interactive user to be prompted for confirmation to send the message, or to reenter compose mode, after being shown a preliminary envelope summary.
  • asksign: โœ๏ธ (Boolean)[Option] Causes the interactive user to be prompted about whether the message is to be signed at the end of each message. The smime-sign variable gets ignored when this one is set.
  • asksub: ๐Ÿท๏ธ (Boolean) Causes S-nail to prompt the interactive user for the subject upon entering compose mode, unless a subject already exists.
  • attrlist: ๐ŸŒˆ A sequence of characters to display in the โ€˜attributeโ€™ column of the headline, as shown in the display of headers. Each character stands for one type of message (see โ€œMessage statesโ€). The default is โ€˜NUROSPMFAT+-$~โ€™, or โ€˜NU *HMFAT+-$~โ€™ if the bsdflags variable is set. The order goes like this:
    • โ€˜Nโ€™: new.
    • โ€˜Uโ€™: unread but old.
    • โ€˜Rโ€™: new but read.
    • โ€˜Oโ€™: read and old.
    • โ€˜Sโ€™: saved.
    • โ€˜Pโ€™: preserved.
    • โ€˜Mโ€™: mboxed.
    • โ€˜Fโ€™: flagged.
    • โ€˜Aโ€™: answered.
    • โ€˜Tโ€™: draft.
    • โ€˜+โ€™: [v15 behaviour may differ] start of a (collapsed) thread in threaded mode (see autosort, thread);
    • โ€˜-โ€™: [v15 behaviour may differ] an uncollapsed thread in threaded mode; only used in conjunction with -L.
    • โ€˜$โ€™: classified as spam.
    • โ€˜~โ€™: classified as possible spam.
  • autobcc: ๐Ÿซฃ Specifies a list of recipients who automatically get a blind carbon copy of each outgoing message.
  • autocc: ๐Ÿ‘€ Specifies a list of recipients who automatically get a carbon copy of each outgoing message.
  • autocollapse: ๐Ÿงต (Boolean) Causes threads to be collapsed automatically when the threaded sort mode (โ€˜threadedโ€™) is entered. See the collapse command.
  • autoprint: ๐Ÿ‘‰ (Boolean) Enables automatic typing of a(n existing) โ€œsuccessiveโ€ message after delete and undelete commands. The message that becomes the new โ€œdotโ€ is shown automatically, as via dp or dt.
  • autosort: ๐Ÿ—‚๏ธ Causes sorted mode (see the sort command) to be entered automatically when a folder is opened, using the value of this variable as the sorting method. For example, set autosort=thread.
  • bang: โ— (Boolean) Enables the substitution of all not (reverse-solidus) escaped exclamation mark โ€˜!โ€™ characters with the contents of the last executed command, for the ! shell escape command and ~!, one of the compose mode โ€œCOMMAND ESCAPESโ€. If this variable is not set, no reverse solidus stripping is performed.
  • bind-timeout: ๐Ÿชฆ [Obsolete] This is the predecessor of bind-inter-byte-timeout. [v15 behaviour may differ] Setting this automatically sets the successor.
  • bind-inter-byte-timeout: โฑ๏ธ [Option] Terminals may generate multi-byte sequences for special function keys, for example, but those sequences may not get read as a unit. Multi-byte sequences can also be defined freely via bind. This variable sets the timeout in milliseconds that the MLE (see โ€œOn terminal control and line editorโ€) waits for more bytes to arrive unless it considers a sequence โ€œcompleteโ€. The default is 200, and the maximum is about 10 seconds. In the following example, the comments state which sequences are affected by this timeout:
? bind base abc echo 0 # abc
? bind base ab,c echo 1 # ab
? bind base abc,d echo 2 # abc
? bind base ac,d echo 3 # ac
? bind base a,b,c echo 4
? bind base a,b,c,d echo 5
? bind base a,b,cc,dd echo 6 # cc and dd
  • bind-inter-key-timeout: ๐Ÿ•ฐ๏ธ [Option] Multi-key bind sequences do not time out by default. If this variable is set, the current key sequence is forcefully terminated once the timeout (in milliseconds) triggers. The value should be (maybe significantly) larger than bind-inter-byte-timeout, but it may not exceed the maximum, too.
  • bsdcompat: ๐ŸŽฉ (Boolean) Sets some cosmetic features to traditional BSD style. It has the same effect as setting askatend and all other variables prefixed with โ€˜bsdโ€™. It also changes the behaviour of emptystart, which does not exist in BSD.
  • bsdflags: ๐Ÿ (Boolean) Changes the letters shown in the first column of a header summary to traditional BSD style.
  • bsdheadline: ๐Ÿ“ฐ (Boolean) Changes the display of columns in a header summary to traditional BSD style.
  • bsdmsgs: ๐Ÿ’ฌ (Boolean) Changes some informational messages to traditional BSD style.
  • bsdorder: ๐Ÿ”€ (Boolean) Causes the โ€˜Subject:โ€™ field to appear immediately after the โ€˜To:โ€™ field in message headers, and with the ~h โ€œCOMMAND ESCAPESโ€.
  • build-cc, build-ld, build-os, build-rest: ๐Ÿ› ๏ธ (Read-only) This is the build environment: the compiler, the linker, and the operating system S-nail has been built for. The OS is usually taken from uname(1) via uname -s and then lowercased. build-rest holds all the possibly interesting rest of the configuration and build environment. This info is also available in the verbose output of the command version. Literal main character energy ๐Ÿ‘‘โœจ

charset-7bit โœจ

okay so this is the value that shows up in the charset= part of Content-Type: MIME headers when S-nail did not convert the message data to another character set ๐Ÿ’… it defaults to US-ASCII, and whatever you pick has to be US-ASCII compatible, no cap ๐ŸŒธ

charset-8bit ๐Ÿฆ‹

[Option] this is the default 8-bit character set that gets tacked on as the implicit last member of the variable sendcharsets ๐Ÿ’œ it defaults to UTF-8 if character set conversion is available, and to ISO-8859-1 otherwise ๐Ÿ˜ญ the exception is when the OS is known to always and exclusively support UTF-8 locales. then the only supported character set is ttycharset and this variable is effectively ignored ๐Ÿ’€

charset-unknown-8bit ๐ŸŒˆ

[Option] okay, so RFC 1428 says when internet mail gateways should "upgrade" a message by using a character set named unknown-8bit ๐Ÿงš since that charset is super unclassified, S-nail can't convert it to any other charset, period ๐Ÿ˜ค if this variable is set, any message part using unknown-8bit is assumed to really be in whatever charset the value says. otherwise, the (final) value of charset-8bit gets used instead โœจ

bestie, this one also kicks in when a MIME type (see "The mime.types files") of a message part using the binary character set gets forcefully treated as text ๐Ÿ™ˆ

cmd ๐Ÿ“

the default value for the pipe command ๐Ÿซถ

colour-disable ๐Ÿ™ˆ

(Boolean) [Option] forcefully disables the usage of colours ๐Ÿ–ค also check out the section "Coloured display" ๐ŸŒˆ

colour-pager ๐ŸŒธ

(Boolean) [Option] whether colour gets used for output that's paged through PAGER ๐ŸŽ€ heads up: pagers may need special command line options. less(1) needs -R and lv(1) needs -c to show colours ๐Ÿ˜ญ honestly you usually don't have to fiddle, since S-nail may make adjustments on its own depending on the value of the environment variable PAGER (see there for more) โœจ

contact-mail, contact-web ๐Ÿ’Œ

(Read-only) the contact addresses for email and web, respectively, for bug reports, suggestions, or anything else about S-nail ๐Ÿซถ the email one can be used directly, like ? eval mail $contact-mail ๐Ÿ’…

content-description-forwarded-message, content-description-quote-attachment, content-description-smime-message, content-description-smime-signature ๐ŸŽ€

[Option] (partially) these are strings that get placed in the matching Content-Description: headers if they're non-empty ๐Ÿฆ‹ they all come with default values, like Forwarded message ๐ŸŒท

crt ๐Ÿ”ฅ

in an (interactive) terminal session, if this variable is set, it becomes the threshold for how many lines the output has to span before it gets shown through the configured PAGER ๐Ÿ“– set it to 0 to force the pager. set it without a value and S-nail will figure out the current height of your terminal screen to work out the threshold (see LINES, screen and stty(1)) ๐Ÿ‘‘ [v15 behaviour may differ]

rn it counts the lines of the message in wire format, which, depending on the mime-encoding, can be totally unrelated to the number of display lines ๐Ÿ™ƒ (the software is old, and historically that relation was just a given, bestie ๐Ÿ’€)

customhdr ๐Ÿงš

this lets you define a set of custom headers to inject into newly composed or forwarded messages โœจ a custom header is the field name, then a colon :, then the field body. standard header field names can't be overwritten by a custom header, except Comments: and Keywords: ๐Ÿ™… different from the command line option -C, the variable value is read as a comma-separated list. to put commas inside header bodies, escape them with a reverse solidus \ ๐Ÿ“ you can manage headers more freely in "Compose mode" via ~^ ๐Ÿ’…

? set customhdr='Hdr1: Body1-1\, Body1-2, Hdr2: Body2'

datefield ๐Ÿ—“๏ธ

this controls the look of the %d date and time format spec of the headline variable, the one used when you view the header summary, for example ๐Ÿ’– if unset, the local receiving date gets used and displayed unformatted. otherwise it uses the message's sending Date: ๐Ÿ“ฌ you can assign a strftime(3) format string to control formatting, but embedding newlines via the %n format isn't supported and will cause display errors ๐Ÿ˜ญ the default is %Y-%m-%d %H:%M, and also see datefield-markout-older ๐ŸŒˆ

datefield-markout-older ๐Ÿฆ‹

only used together with datefield ๐Ÿซถ this creates a visible distinction for messages dated more than a day in the future, or older than six months. it's kinda like the -l option of the POSIX utility ls(1) ๐Ÿ‘€ if set to the empty string, you get the plain month, day and year of the Date:, but you can still assign a strftime(3) format string to control formatting ๐Ÿ’… the default is %Y-%m-%d โœจ

debug ๐Ÿงช

(Boolean) (Almost) this enters a debug-only sandbox mode that generates lots of log messages, disables the actual delivery of messages, and also implies norecord as well as nosave ๐Ÿ˜ฑ also see verbose ๐Ÿ’œ

disposition-notification-send ๐Ÿ“จ

(Boolean) [Option] emits a Disposition-Notification-To: header (RFC 3798) with the message ๐Ÿงพ this requires the from variable to be set ๐Ÿ”ฅ

dot ๐ŸŒธ

(Boolean) when dot is set, a period . on a line by itself during message input in "Compose mode" (interactive or batch -#) counts as end-of-message, on top of the normal end-of-file condition ๐Ÿซถ this behaviour is implied in posix mode with a set ignoreeof ๐Ÿ™ˆ

dotlock-disable ๐Ÿ”’

(Boolean) [Option] disables creation of "dotlock files" for MBOX databases ๐Ÿ“

dotlock-ignore-error โš ๏ธ

[Obsolete] (Boolean) [Option] ignores failures when creating "dotlock files" ๐Ÿ’€ please use dotlock-disable instead ๐Ÿซถ

editalong โœ๏ธ

if this variable is set, the editor starts automatically when a message gets composed in interactive mode ๐ŸŽ€ if the value starts with the letter v, it acts as if ~v had been specified. otherwise it acts as if ~e had been specified (see "COMMAND ESCAPES") ๐Ÿ’… the editheaders variable is implied for this automatically spawned editor session ๐Ÿฆ‹

editheaders ๐Ÿ“

(Boolean) when a message gets edited while being composed, its header is included in the editable text ๐ŸŒท

emptystart ๐ŸŒ™

(Boolean) when entering interactive mode, S-nail normally writes "No mail for user" and exits immediately if a mailbox is empty or doesn't exist ๐Ÿ˜ญ if this variable is set, S-nail starts even with an empty or non-existent mailbox (the latter behaviour furtherly depends upon bsdcompat, though) ๐Ÿงš

errexit ๐Ÿšจ

(Boolean) every command with a non-0 exit status, including every called macro that returns a non-0 status, causes a program exit unless it's prefixed by ignerr (see "Command modifiers") ๐Ÿ’€ this also affects "COMMAND ESCAPES", but those use a different modifier for ignoring the error ๐Ÿ™ƒ for more on this topic, refer to the variable ? ๐Ÿ’œ

errors-limit ๐Ÿ“‹

[Option] the maximum number of entries in the errors queue ๐Ÿซถ

escape ๐Ÿš

the first character of this value defines the escape character for "COMMAND ESCAPES" in "Compose mode" ๐Ÿ’… the default is the tilde ~ ๐ŸŒˆ if set to the empty string, command escapes are disabled ๐Ÿ™…

expandaddr ๐Ÿ’Œ

okay this one's a whole vibe ๐ŸŒ€ if unset, only user name and email address recipients are allowed "On sending mail, and non-interactive mode" ๐Ÿ“ญ if set without a value, all possible recipient types will be accepted ๐Ÿซถ

a value is parsed as a comma-separated list of case-insensitive strings. if that list contains restrict, the behaviour acts like the unset one, except when you're in interactive mode, or when "COMMAND ESCAPES" were enabled via -~ or -#. then it acts like the set-without-value one and allows all address types ๐ŸŽ€ restrict really acts like restrict,-all,+name,+addr, so be careful with ordering issues ๐Ÿ˜ตโ€๐Ÿ’ซ

recipient types can be added with a plus sign + prefix, or removed with a hyphen-minus - prefix ๐ŸŽฏ by default, invalid or disallowed types get filtered out and cause a warning. hard send errors need to be enforced by including fail ๐Ÿšจ here's what the values mean:

  • all โ€” covers all types ๐ŸŒ
  • fcc โ€” whitelists Fcc: header targets regardless of other settings ๐Ÿ’Œ
  • file โ€” file targets (it includes fcc) ๐Ÿ“
  • pipe โ€” command pipeline targets ๐Ÿ”ง
  • name โ€” user names still unexpanded after alias and mta-aliases processing, and thus left for expansion by the mta (invalid for the built-in SMTP one) ๐Ÿงธ
  • addr โ€” network addresses ๐ŸŒ

targets are interpreted in the given order, so restrict,fail,+file,-all,+addr will cause hard errors for any non-network address recipient, unless you're running interactively or started with the option -~ or -#. in the latter case(s), any type may be used ๐Ÿ‘‘

Okay so, the expandaddr thing is still going ๐Ÿ’…. Plain local user names can get expanded into fully qualified network addresses ๐ŸŒ (also see hostname) if you slip nametoaddr into the list ๐Ÿฆ‹. Historically, invalid recipients just got quietly stripped off with zero errors ๐Ÿ’€, but you can change that by making failinvaddr an entry in the list (it really acts like failinvaddr,+addr) ๐Ÿ™ˆ. Then there's domaincheck (really domaincheck,+addr), which checks address domain names against a whitelist and strips out addressees that fail (fail for hard errors) ๐Ÿšซ. The domain localhost and the non-empty value of hostname (the real hostname otherwise) are always whitelisted ๐Ÿ’–, and expandaddr-domaincheck can extend that list. Last but not least, some address providers (for example -b, -c and all other command line recipients) get evaluated as if they were specified within dollar-single-quotes (see โ€œShell-style argument quotingโ€) if the value list contains the string shquote โœจ.

expandaddr-domaincheck ๐ŸŒธ

This can be set to a comma-separated list of domain names that should be whitelisted for the evaluation of the domaincheck mode of expandaddr. Heads up: IDNA encoding is not automatically performed, so addrcodec can be used to prepare the domain (of an address) ๐Ÿงš.

expandargv ๐Ÿ”’

Unless this variable is set, extra mta (Mail-Transfer-Agent) arguments from the command line (the stuff that can be given after a -- separator) cause the program to terminate with failure status ๐Ÿšจ. You can get the same effect by using the special (case-insensitive) value fail ๐Ÿ™…โ€โ™€๏ธ. A slightly less strict vibe is the otherwise identical restrict, which does accept those arguments in interactive mode, or if tilde commands were enabled explicitly by using one of the command line options -~ or -# ๐ŸŽ€. The empty value allows unconditional usage ๐Ÿฆ‹.

features ๐Ÿ“‹

(Read-only) This is a string giving a list of optional features โœจ. Features get a plus sign + in front if they're available, and a hyphen-minus - otherwise ๐Ÿ’œ. To make substring matching easy, the string starts and ends with a comma ๐Ÿซถ. The output of the command version includes this info in a much prettier format ๐Ÿ’….

flipr ๐Ÿ”

(Boolean) This setting flips the meanings of a set of reply commands ๐Ÿ™ƒ. The lowercase variants, which by default address all recipients included in the header of a message (reply, respond, followup), become the uppercase variants, which by default address the sender only (Reply, Respond, Followup), and vice versa ๐ŸŒˆ. Iconic reversal, honestly ๐Ÿ‘‘.

folder ๐Ÿ“

This is the default path under which mailboxes are saved ๐Ÿ—‚๏ธ. Filenames that begin with the plus sign + have the plus sign replaced with the value of this variable if it's set ๐ŸŒธ. Otherwise the plus sign stays unchanged when doing โ€œFilename transformationsโ€ ๐Ÿฆ‹. Also see folder for more on this topic, and know about the standard-imposed implications of outfolder ๐Ÿ“œ. The value supports a subset of transformations itself, and if the non-empty value does not start with a solidus /, then the value of HOME gets prefixed automatically ๐Ÿ . Once the actual value is evaluated, the internal variable folder-resolved gets updated for caching purposes ๐Ÿ’พ.

folder-hook-FOLDER, folder-hook ๐Ÿช

This names a defined macro that gets called whenever a folder is opened โœจ. The macro also gets invoked when new mail arrives, but message lists for commands executed from the macro only include the newly arrived messages then ๐Ÿ“ฌ. localopts are activated by default in a folder hook, which means the covered settings get reverted once you leave the folder again ๐Ÿ”„.

The specialized form overrides the generic one if FOLDER matches the file that gets opened ๐ŸŽฏ. Unlike other folder specifications, the fully expanded name of a folder, without metacharacters, is used to avoid ambiguities ๐Ÿง. However, if the mailbox resides under folder, then the usual + specification is tried in addition ๐Ÿ’โ€โ™€๏ธ. So if folder is โ€œmailโ€ (and thus relative to the user's home directory), then /home/usr1/mail/sent gets tried as folder-hook-/home/usr1/mail/sent first, and then followed by folder-hook-+sent ๐ŸŒŸ.

folder-resolved ๐Ÿง 

(Read-only) This gets set to the fully resolved path of folder once that evaluation has happened ๐Ÿ”. It's pretty internal, so don't stress about it ๐Ÿ™ˆ.

followup-to ๐Ÿ’Œ

(Boolean) This controls whether a Mail-Followup-To: header gets generated when sending messages to known mailing lists ๐Ÿ“จ. The user, as determined via from (or, if that contains multiple addresses, sender), will be placed in there if any list addressee is not a subscribed list ๐Ÿ’œ. Also see followup-to-honour and the commands mlist, mlsubscribe, reply and Lreply ๐Ÿซถ.

followup-to-add-cc ๐Ÿ‘ฏ

(Boolean) This controls whether the user gets added to the messages' Cc: list in addition to getting an entry in Mail-Followup-To: (see followup-to) ๐Ÿ’–.

followup-to-honour ๐Ÿฏ

This controls whether a Mail-Followup-To: header is honoured when group-replying to a message via reply or Lreply ๐Ÿ™Œ. It's a โ€œquadoptionโ€; if set without a value, it defaults to โ€œyesโ€, and see followup-to ๐ŸŒธ.

forward-add-cc ๐Ÿ“ฎ

(Boolean) This decides whether senders of messages forwarded via ~F, ~f, ~m, ~U or ~u get made members of the carbon copies Cc: list ๐Ÿ’….

forward-as-attachment ๐Ÿ“Ž

(Boolean) Original messages are normally sent as inline text with the forward command, and only the first part of a multipart message is included ๐Ÿ˜ฌ. With this setting enabled, messages get sent as unmodified MIME message/rfc822 attachments with all of their parts included ๐ŸŽ€.

forward-inject-head, forward-inject-tail ๐Ÿ’‰

These are the strings to put before and after the text of a message with the forward command, respectively ๐Ÿ’ซ. The former defaults to -------- Original Message --------\n ๐Ÿ“. Special format directives in these strings get expanded if possible, and if so configured, the output gets folded according to quote-fold; for more, please refer to quote-inject-head ๐Ÿ“–. Injections will not be performed by forward if the variable forward-as-attachment is set โ€” the โ€œCOMMAND ESCAPESโ€ ~F, ~f, ~M, ~m, ~U, ~u always inject ๐Ÿšจ.

from ๐Ÿ’Œ

This is the address (or a list of addresses) to put into the From: field of the message header ๐Ÿ“. Quoting RFC 5322, these are the author(s) of the message, meaning the mailbox(es) of the person(s) or system(s) responsible for writing it ๐Ÿ–‹๏ธ. According to that RFC, the sender variable is required if from contains more than one address โš ๏ธ. [v15 behaviour may differ] Please expect automatic management of the from and sender relationship ๐Ÿค–. Depending on the context, these addresses get handled as if they were in the list of alternates ๐Ÿ’โ€โ™€๏ธ.

If a file-based MTA is used, then from (or, if that contains multiple addresses, sender) can still be used as the envelope sender address at the MTA protocol level (the RFC 5321 reverse-path) ๐Ÿ“ฌ. You can do that either via the -r command line option (without argument; see there for more), or by setting r-option-implicit ๐Ÿ”ง.

If the machine's hostname isn't valid on the Internet (for example, at a dialup machine), then either this variable or hostname has to be set ๐Ÿšจ. ([v15-compat] an SMTP-based mta adds even more fine-tuning with smtp-hostname.) If so, the message and MIME part related unique ID fields Message-ID: and Content-ID: will be created, except when disallowed by message-id-disable or stealthmua ๐Ÿ™ˆ.

fullnames ๐Ÿชช

(Boolean) For historical reasons, comments and name parts of email addresses get removed by default when sending mail, replying to or forwarding a message ๐Ÿ™„. If this variable is set, that stripping is not performed โœจ.

fwdheading ๐Ÿ•ฐ๏ธ

[Obsolete] This is the predecessor of forward-inject-head ๐Ÿ‘ต.

header ๐Ÿงพ

(Boolean) This causes the header summary to be written at startup and after commands that affect the number of messages or the order of messages in the current folder ๐Ÿ“‹. Unless in posix mode, a header summary also gets displayed on folder changes ๐Ÿ”€. The command line option -N can be used to set noheader ๐Ÿ™…โ€โ™€๏ธ.

headline ๐Ÿ“

This is a format string to use for the summary of headers ๐ŸŽจ. Format specifiers in the given string start with a percent sign % and may be followed by an optional decimal number indicating the field width ๐Ÿ”ข. If that number is negative, the field is left-aligned โฌ…๏ธ. Names and addresses are subject to modifications according to showname and showto ๐Ÿ‘€. Valid format specifiers are:

  • %%: a plain percent sign, nothing extra ๐Ÿ’…
  • %>: the dotmark ๐ŸŒธ. It's a space character, except for the current message (the "dot"), where it shows >. This depends on headline-plain.
  • %<: the other dotmark ๐Ÿฆ‹. Same deal as %>, but for the current message it shows <. This also depends on headline-plain.
  • %$: [Option] the spam score of the message, as classified by the command spamrate ๐Ÿ”ฅ. If there's no spam support, you only get a replacement character ๐Ÿ™ˆ.
  • %a: the message attribute character (status flag) ๐Ÿท๏ธ. You can change what it shows by setting attrlist.
  • %d: the date from the Date: header when datefield is set (that's the default) ๐Ÿ“…. Otherwise you get the date the message was received. You can format it by assigning a strftime(3) format string to datefield (and datefield-markout-older).
  • %e: the indenting level in threaded sort mode ๐Ÿงต
  • %f: the address of the message sender ๐Ÿ’Œ
  • %i: the message thread tree structure ๐ŸŒณ. Heads up, this one doesn't support a field width, and it honours headline-plain.
  • %L: mailing list status ๐Ÿ“ฎ. Is the addressee a known l (mlist) or L mlsubscribed mailing list? The letter P announces that there's an RFC 2369 List-Post: header, which makes the message a juicy target for Lreply ๐Ÿ’œ.
  • %l: the number of lines in the message, if available ๐Ÿ“
  • %m: message number ๐Ÿ”ข
  • %o: the number of octets (bytes) in the message, if available ๐Ÿ“ฆ
  • %S: the message subject (if any) in double quotes ๐ŸŽ€
  • %s: the message subject (if any), no quotes โœจ
  • %t: the position in threaded/sorted order ๐Ÿงš
  • %U: 0 unless you're in an IMAP mailbox, where it expands to the UID of the message ๐Ÿ†”

The default is %>%a%m %-18f %16d %4l/%-5o %i%-s, or %>%a%m %20-f %16d %3l/%-5o %i%-S if bsdcompat is set. Also see attrlist, headline-plain and headline-bidi ๐Ÿ’–.

๐ŸŒ headline-bidi

Okay, bidirectional text is a whole mood ๐Ÿ˜ญ. In headers, numbers like dates or file sizes don't flip the current text direction, so arabic or other right-to-left text ends up with ugly, messy line layouts. The catch is that only a minority of terminals can handle direction changes properly, so you have to step in yourself for it to look good โœจ. Also, extended host system support is required, and detection of the terminal character set is one precondition. This feature only works in a Unicode (UTF-8) locale ๐Ÿซถ.

Setting this variable makes S-nail wrap the text fields in the headline (and some other fields, like dynamic expansions in the prompt) with special Unicode control sequences ๐ŸŽ€. You can fine-tune how much terminal support it assumes by assigning a value:

  • No value at all (or any value other than 1, 2 and 3) makes S-nail assume your terminal can properly deal with Unicode version 6.3 ๐Ÿ’…. The text gets embedded in a pair of U+2068 (FIRST STRONG ISOLATE) and U+2069 (POP DIRECTIONAL ISOLATE) characters, and no space on the line is reserved for them.
  • 1: Unicode 6.3 again, but this time room for two spaces gets reserved on the line for the control sequences ๐Ÿงธ.
  • 2: Unicode 1.1 support, using U+200E (LEFT-TO-RIGHT MARK) ๐ŸŒธ.
  • 3: also Unicode 1.1 support with U+200E (LEFT-TO-RIGHT MARK), but this one reserves room for two spaces on top of that ๐Ÿฆ‹.

๐ŸŽจ headline-plain

(Boolean) On Unicode (UTF-8) aware terminals, enhanced graphical symbols are used by default for certain headline entries. If you set this variable, only basic US-ASCII symbols will be used ๐Ÿ“.

๐Ÿ“œ history-file

[Option] The (expandable) location of a permanent history file for the MLE line editor (see โ€œOn terminal control and line editorโ€). Also see history-size ๐Ÿ’œ.

๐Ÿ“š history-gabby

[Option] Adds more entries to the MLE history than usual. It takes a comma-separated list of case-insensitive strings, so you can fine-tune which gabby entries are allowed ๐Ÿ—ฃ๏ธ. If it contains errors, erroneous commands get added too. all adds all the optional entries, and it's the fallback chattiness identifier of on-history-addition ๐Ÿ’….

๐Ÿค history-gabby-persist

(Boolean)[Option] The history-gabby entries won't be saved in persistent storage unless this variable is set. Knowing whether a persistent entry was gabby isn't lost though. Also see history-file ๐Ÿ”’.

๐Ÿ“ history-size

[Option] Setting this imposes a limit on the number of concurrent history entries. Set it to the value 0 and no further history entries get added. Loading and incorporating the history-file upon program startup can also be suppressed by doing this ๐Ÿšซ. Runtime changes won't be reflected until the history is saved or loaded (again) โณ.

๐Ÿ“ญ hold

(Boolean) This setting controls whether messages are held in the system inbox, and it's set by default โœจ.

๐Ÿ  hostname

Used instead of the value obtained from uname(3) and getaddrinfo(3) as the hostname when expanding local addresses, for example in From: (also see โ€œOn sending mail, and non-interactive modeโ€, for expansion of addresses that have a valid user-, but no domain name in angle brackets) ๐ŸŒ. If either from or this variable is set, the message and MIME part related unique ID fields Message-ID: and Content-ID: will be created, except when disallowed by message-id-disable or stealthmua ๐Ÿ†”. If the [Option]al IDNA support is available (see idna-disable), variable assignment is aborted when a necessary conversion fails ๐Ÿ’ฅ.

Setting it to the empty string makes the normal hostname get used, but it still enables creation of said ID fields ๐Ÿซง. [v15-compat] In conjunction with the built-in SMTP mta smtp-hostname, it also influences the results. So one should produce some test messages with the desired combination of hostname, and/or from, sender etc. first ๐Ÿงช.

๐ŸŒ idna-disable

(Boolean)[Option] Can be used to turn off the automatic conversion of domain names according to the rules of IDNA (internationalized domain names for applications) ๐ŸŒ. Since the IDNA code assumes that domain names are specified with the ttycharset character set, a UTF-8 locale charset is required to represent all possible international domain names (before conversion, that is) ๐Ÿ’ซ.

โœ‚๏ธ ifs

The input field separator that is used ([v15 behaviour may differ] by some functions) to decide where to split input data ๐Ÿ”ช.

  1. Unsetting it is treated as assigning the default value, ' \t\n'.
  2. If set to the empty value, no field splitting will be performed at all ๐Ÿ™….
  3. If set to a non-empty value, all whitespace characters are extracted and assigned to the variable ifs-ws.
    • a. ifs-ws will be ignored at the beginning and end of input. Diverging from POSIX shells default whitespace is removed in addition, which is owed to the entirely different line content extraction rules ๐Ÿงน.
    • b. Each occurrence of a character of ifs will cause field-splitting, and any adjacent ifs-ws characters will be skipped โœจ.

๐Ÿ”’ ifs-ws

(Read-only) Automatically deduced from the whitespace characters in ifs ๐Ÿค–.

๐Ÿ™ˆ ignore

(Boolean) Ignores interrupt signals from the terminal while you're entering messages. Instead, they get echoed as @ characters and the current line gets discarded ๐Ÿ’€.

๐Ÿšซ ignoreeof

(Boolean) Ignores end-of-file conditions (control-D) in โ€œCompose modeโ€ on message input and in interactive command input ๐Ÿ›‘. If set, an interactive command input session can only be left by explicitly using one of the commands exit and quit. Message input in compose mode can only be terminated by entering a period . on a line by itself, or by using ~. from โ€œCOMMAND ESCAPESโ€. Setting this implies the behaviour that dot describes in posix mode ๐Ÿซถ.

๐Ÿ“ฌ inbox

If this is set to a non-empty string, it specifies the user's โ€œprimary system mailboxโ€ ๐Ÿ’Œ. It overrides MAIL and the system-dependent default, and (thus) gets used to replace % when doing โ€œFilename transformationsโ€. Also see folder for more on this topic ๐Ÿ“‚. The value supports a subset of transformations itself ๐Ÿ”.

๐Ÿ“ indentprefix

String used by the ~m, ~M and ~R โ€œCOMMAND ESCAPESโ€, and by the quote option, for indenting messages. It replaces the POSIX mandated default tabulator character '\t' ๐ŸŽ€. Also see quote-chars ๐Ÿ’–.

keep ๐ŸŽ€

(Boolean) okay so, if this is set, an empty โ€œprimary system mailboxโ€ file doesn't get deleted ๐Ÿงธ ngl this is so important. With posix mode on, any empty file gets removed unless this variable is set. It can help other mail user agents play nicely when you're all sharing one folder directory ๐Ÿซถ, and it stops sneaky people from making fake mailboxes in a world-writable spool directory ๐Ÿ’€. [v15 behaviour may differ] Only local regular (MBOX) files are protected. Maildir and other mailbox types are never removed, even when they're empty โœจ.

keep-content-length ๐Ÿ’œ

(Boolean) when you're editing messages and writing MBOX mailbox files, S-nail can keep the Content-Length: and Lines: header fields that some MUAs generate, as long as you set this variable ๐Ÿฆ‹. S-nail doesn't use or update these non-standardized fields (which honestly shows one of their conceptual problems ๐Ÿ’…), so stripping them should make things more interoperable between MUAs that work with the same mailbox files ๐ŸŒธ. Heads up: if this isn't set but writebackedited is, the automatic stripping that happens will already mark the message as modified ๐Ÿ‘€. [v15 behaviour may differ] Someday S-nail will be able to rewrite and apply a MIME encoding to modified messages, and then those fields will get stripped silently ๐Ÿงš.

keepsave ๐Ÿ“

(Boolean) when you save a message, it's normally thrown out of the folder it came from once you quit S-nail. This setting keeps every saved message around, so nothing gets tossed ๐Ÿซถ.

line-editor-cpl-word-breaks โญ

[Option] this is the list of bytes that the mle-complete tabulator completion uses to figure out where word boundaries are. By default it's "'@=;|: ๐ŸŽ€ [v15 behaviour may differ] This mechanism is still pretty limited, so don't expect miracles yet ๐Ÿ™ˆ.

line-editor-disable ๐ŸŒˆ

(Boolean) turns off any line editing capabilities from S-nail's POW ๐Ÿ’…. Check out โ€œOn terminal control and line editorโ€ if you want the full tea โ˜•.

line-editor-no-defaults ๐Ÿฆ‹

(Boolean)[Option] this one stops S-nail from setting up any default key bindings, so you're on your own with the keys ๐Ÿ”ฅ.

log-prefix ๐Ÿ’–

Error log messages get this prefix string, which is s-nail: ๐Ÿ“ (yes, there's a trailing space, it's giving precision โœจ).

mailbox-display ๐ŸŒธ

(Read-only) this is the name of the current mailbox (folder), possibly shortened so it's easier to read on screen ๐Ÿ‘€.

mailbox-resolved ๐Ÿงš

(Read-only) this is the fully resolved path of the current mailbox, no shortcuts, no drama ๐Ÿ—บ๏ธ.

mailcap-disable ๐ŸŽ€

(Boolean)[Option] turns off using MIME type handlers from the โ€œThe Mailcap filesโ€, and stops them from being loaded automatically ๐Ÿ™…โ€โ™€๏ธ.

mailx-extra-rc ๐Ÿ’œ

This is an extra startup file that gets loaded last, after the โ€œResource filesโ€ ๐Ÿงธ. Put commands in here that other POSIX mailx(1) implementations don't understand, which basically means anything not covered by โ€œInitial settingsโ€ ๐Ÿ’….

markanswered โœจ

(Boolean) when you reply to a message and this variable is set, that message gets marked as answered ๐Ÿ’Œ. See the section โ€œMessage statesโ€ for more ๐Ÿฆ‹.

mbox-fcc-and-pcc ๐Ÿ”ฅ

(Boolean) by default, all file and pipe message receivers (see expandaddr) get valid MBOX database entry message data (see folder, mbox-rfc4155), and existing file targets get extended so they follow RFC 4155 ๐Ÿซถ. If you unset this, you get a plain standalone RFC 5322 message instead, and existing file targets get overwritten ๐Ÿ˜ญ so be careful!

mbox-rfc4155 ๐ŸŒˆ

(Boolean) when S-nail opens MBOX mailbox databases, it uses the super tolerant POSIX rules to find message boundaries (the so-called From_ lines) instead of the stricter rules from RFC 4155 ๐Ÿ’…. This is for compatibility with old software ๐Ÿง“. You can flip this behaviour by setting this variable โœจ.

This can be handy temporarily when S-nail complains about invalid From_ lines while opening an MBOX ๐Ÿ™ˆ. Set this variable, reopen the mailbox in question, and it might fix things ๐Ÿฉน. If it does, copying the whole mailbox to another file, like copy * SOME-FILE, will do proper, all-compatible From_ quoting for every message it detects, and you'll end up with a valid MBOX mailbox ๐ŸŽ€. [v15 behaviour may differ] The better, non-destructive approach is to re-encode invalid messages as if they were created anew, instead of mangling the From_ lines ๐Ÿ‘‘. That needs the structural code changes from the v15 rewrite ๐Ÿ› ๏ธ. Finally, you can unset the variable again when you're done ๐Ÿงน:

define mboxfix {
  localopts yes; wysh set mbox-rfc4155;\
    wysh File "${1}"; copy * "${2}"
}
call mboxfix /tmp/bad.mbox /tmp/good.mbox

memdebug ๐Ÿงช

(Boolean) this is an internal development variable ๐Ÿ› ๏ธ. It keeps memory debugging enabled even when debug isn't set ๐Ÿ”.

message-id-disable ๐Ÿฆ‹

(Boolean) setting this variable completely suppresses the generation of the Message-ID: and Content-ID: headers for messages and MIME parts ๐ŸŒธ. That basically leaves the job up to the mta (Mail-Transfer-Agent) or the SMTP server ๐Ÿซถ. Just a heads up: RFC 5321 doesn't require an SMTP server to add this field itself, so make sure yours accepts messages without a Message-ID ๐Ÿ‘€.

message-inject-head ๐Ÿ“

A string that goes at the very beginning of each new message, followed by a newline โœจ. [Obsolete] This one is on its way out ๐Ÿฅ€. The escape sequences tabulator \t and newline \n are understood, but use the wysh prefix when you set the variable(s) instead ๐Ÿ’….

message-inject-tail ๐ŸŒˆ

A string that goes at the very end of each new message, followed by a newline ๐Ÿ’Œ. [Obsolete] Also on its way out ๐Ÿฅ€. The escape sequences tabulator \t and newline \n are understood, but use the wysh prefix when you set the variable(s) instead ๐Ÿ’…. Also see on-compose-leave ๐Ÿฆ‹.

metoo ๐Ÿ’–

(Boolean) usually, when an alias expansion includes the sender, the sender gets removed from the expansion ๐Ÿ™…โ€โ™€๏ธ. Setting this option stops those removals ๐Ÿซถ. Heads up: a set metoo also passes a -m option through to the mta (Mail-Transfer-Agent) ๐Ÿš€. Most modern MTAs don't document this flag anymore, but no MTA is known to not support it (for historical compatibility) ๐Ÿง“.

mime-allow-text-controls ๐ŸŽ€

(Boolean) when you send messages, every part gets MIME-inspected so S-nail can figure out the Content-Type: and Content-Transfer-Encoding: it needs to travel over mail transport (see mime-encoding) ๐Ÿ”. It's kind of like what file(1) does with the --mime option ๐Ÿง.

Here's the drama ๐Ÿ˜ญ: this classification treats text files encoded in UTF-16 (which you'll see a lot with HTML files) and similar character sets as binary octet-streams. It forcefully changes any text/plain or text/html spec to application/octet-stream ๐Ÿ’€. When that happens, a charset MIME parameter that wasn't set yet gets set to binary, which makes it impossible for the receiving MUA to automatically interpret the contents ๐Ÿ™ˆ.

If you set this variable, and the data was unambiguously identified as text at first glance (through a .txt or .html file extension), then the original Content-Type: won't be overwritten ๐Ÿซถ.

mime-alternative-favour-rich ๐ŸŒธ

(Boolean) if this is set, rich MIME alternative parts (like HTML) get preferred over the plain text versions included with them when messages are displayed ๐Ÿ’…. This only happens when a handler exists that produces output which can be (re)integrated into S-nail's normal visual display โœจ.

mime-counter-evidence ๐Ÿงš

Normally, the Content-Type: field decides how MIME parts get handled ๐Ÿ“ฆ. But some MUAs don't use โ€œThe mime.types filesโ€ (also see โ€œHTML mail and MIME attachmentsโ€) or anything similar to classify content properly ๐Ÿ™ƒ. Instead they label even plain text attachments as the unspecific application/octet-stream ๐Ÿ˜ค. If this variable is set, S-nail will try to re-classify those MIME parts when it can, for example through a possibly existing attachment filename ๐Ÿ•ต๏ธโ€โ™€๏ธ.

A non-empty value can also be given, and it should be a number that acts as a carrier of bits ๐Ÿงฎ. It's best specified as a binary value, like 0b1111 ๐Ÿค“. Here's what each bit does:

  • Bit two (counting from 1, decimal 2): the detected mimetype gets carried along with the message and used to decide which MIME handler to use ๐Ÿฆ‹. When you display that MIME part, the part-info shows the overridden content-type with a plus sign + โž•.
  • Bit three (decimal 4): the counter-evidence is always produced, and a positive result gets used as the MIME type ๐Ÿ‘‘. It even forcefully overrides the part's given MIME type ๐Ÿ’ฅ.
  • Bit four (decimal 8): as a last resort, the actual content of application/octet-stream parts gets inspected, so data that looks like plain text can be treated as plain text ๐Ÿ“. This mode is even more relaxed when the data is displayed to the user or used as a message quote ๐Ÿซ . (The data consumers that mangle data for display purposes, like masking control characters, are a good example ๐Ÿ™ˆ.)

mime-encoding ๐ŸŒˆ

This is the MIME Content-Transfer-Encoding to use in outgoing text messages and message parts, wherever it applies ๐Ÿ’Œ. Plain 7-bit clean text messages go without an encoding whenever that's possible โœจ:

  • 8bit (or 8b) ๐Ÿซฃ: this one just passes your raw data straight through, totally unchanged โœจ. It's lowkey risky, though, because it can cause problems when mail goes over channels that aren't ESMTP (RFC 1869) compliant. A few input data constructs aren't allowed by the specs either, and they might make a different transfer-encoding get picked instead ๐Ÿ’€. By established rules and popular demand, occurrences of ^From_ (see mbox-rfc4155) get MBOXO quoted, meaning they're prefixed with a greater-than sign >. That happens instead of switching to a non-destructive encoding like quoted-printable, unless something like message signing needs otherwise ๐Ÿ”ฅ.
  • quoted-printable (or qp) ๐Ÿ’–: this one is 7-bit clean, and ASCII characters pass through untouched, so an English message can be read as-is ๐Ÿ“–. It's also fine for other single-byte locales that share a lot of characters with ASCII, like ISO-8859-1. The catch is the overhead for other character sets. It can take up to twelve (12) bytes to encode a single four (4) byte UTF-8 character, which is a lot ๐Ÿ˜ญ. This is the default encoding ๐Ÿ‘‘.
  • base64 (or b64) ๐Ÿงš: also 7-bit clean, and it's always used for binary data ๐Ÿฆ‹. It has a constant input:output ratio of 3:4, so no matter the character set, three bytes of input become four bytes of output. It's not human readable without performing a decoding step ๐Ÿ™ˆ.

mime-force-sendout

(Boolean) [Option] ๐Ÿ›Ÿ For when failing to send is simply not an option because of non-convertible character content. Set this variable and, as a last resort, the part content gets classified as application/octet-stream. For the full tea on character set conversion, check the section โ€œCharacter setsโ€, and for handling part content internally or externally, check โ€œHTML mail and MIME attachmentsโ€ ๐Ÿ’….

mimetypes-load-control

This is your control panel for which โ€œThe mime.types filesโ€ get loaded ๐ŸŽ›๏ธ. If the letter u is in the value, your personal ~/.mime.types file gets loaded (if it exists). The letter s does the same job for the system wide /etc/mime.types. Directives in the user file take precedence, and letter matching is case-insensitive ๐Ÿง. If this variable isn't set, S-nail will try to load both files. The S-nail-built-in MIME types can't be suppressed, but they get matched last. You can list the order with mimetype ๐ŸŒธ.

Want more sources? Use a different syntax. If the value string contains an equals sign =, it's parsed as a comma-separated list of those letters plus f=FILENAME pairs. The given filenames get expanded and loaded, and their content may use the extended syntax described in the section โ€œThe mime.types filesโ€ โœจ. Directives found in those files always take precedence, meaning they're prepended to the MIME type cache ๐Ÿš€.

mta

Pick an alternate Mail-Transfer-Agent by giving either the full pathname of an executable (a file:// prefix is optional), or, [Option]ally, a SMTP aka SUBMISSION protocol URL [v15-compat]:

submissions://[user[:password]@]server[:port]

([no v15-compat]: [smtp://]server[:port].) The default is chosen at compile time ๐Ÿ”ง. MTA data transfers always run in asynchronous child processes, and they go unsupervised unless the sendwait or the verbose variable is set ๐Ÿ‘€. Also see mta-bcc-ok. [Option]ally, you can do expansion of aliases(5) by setting mta-aliases ๐Ÿ“.

For testing, there's the test pseudo-MTA ๐Ÿงช. It dumps to standard output, or optionally to a file, and it honours mbox-fcc-and-pcc:

$ echo text | s-nail -:/ -Smta=test -s ubject ex@am.ple
$ </dev/null s-nail -:/ -Smta=test://./xy ex@am.ple

For a file-based MTA, you might need to set mta-argv0 in order to choose the right target of a modern mailwrapper(8) environment ๐ŸŽ€. It gets command line arguments from a few possible sources: from the variable mta-arguments if it's set, and from the command line if one is given and the variable expandargv allows their use. Argument processing of the MTA ends with a -- separator ๐Ÿ›‘.

You can turn off the implicit MTA command line arguments by setting the boolean variable mta-no-default-arguments. That also disables passing -- to the MTA ๐Ÿ™…โ€โ™€๏ธ. The ones it covers are -i (for not treating a line with only a dot . character as the end of input), -m (shall the variable metoo be set), and -v (if the verbose variable is set). In conjunction with the -r command line option or r-option-implicit -f, as well as possibly -F, these will (not) be passed.

โš ๏ธ Heads up: the SMTP and SUBMISSION protocols are a whole thing, so read these variants carefully ๐Ÿ’•.

[Option]ally, S-nail can send mail over SMTP aka SUBMISSION network connections to a single defined smart host ๐Ÿ“ฎ. Do this by setting this variable to a SMTP or SUBMISSION URL (see โ€œOn URL syntax and credential lookupโ€). You can specify an authentication scheme via the variable chain smtp-auth ๐Ÿ”. Encrypted network connections are [Option]ally available, and the section โ€œEncrypted network communicationโ€ gives an overview with links to more info ๐Ÿ”’. With some mail providers, you may need to set the smtp-hostname variable in order to use a specific combination of from, hostname and mta ๐Ÿซถ. Network communication socket timeouts are configurable via socket-connect-timeout โฑ๏ธ. All generated network traffic may be proxied over a SOCKS socks-proxy, and it can be logged by setting verbose twice ๐Ÿ“œ. The following SMTP variants may be used:

  • The plain SMTP protocol (RFC 5321) ๐Ÿ“ฌ normally lives on server port 25 and requires setting the smtp-use-starttls variable to enter a TLS encrypted session state. Assign a value like [v15-compat] smtp://[user[:password]@]server[:port] ([no v15-compat] smtp://server[:port]) to choose this protocol.
  • SMTPS ๐ŸŒ™ is supposed to live on server port 465 and is automatically TLS secured. Unfortunately, it never became a standardized protocol, so your host's network service database may not support it. In fact, the port number has already been reassigned to other protocols! ๐Ÿ’€ It's still a commonly offered protocol, though, so you can choose it by assigning a value like [v15-compat] smtps://[user[:password]@]server[:port] ([no v15-compat] smtps://server[:port]). Because of the problems mentioned, it's usually necessary to explicitly specify the port as :465, however ๐Ÿ™ƒ.
  • The SUBMISSION protocol (RFC 6409) ๐Ÿ“จ lives on server port 587 and is identical to the SMTP protocol from S-nail's point of view. It requires setting smtp-use-starttls to enter a TLS secured session state. For example: [v15-compat] submission://[user[:password]@]server[:port].
  • The SUBMISSIONS protocol (RFC 8314) ๐Ÿฆ‹ lives on server port 465 and is TLS secured by default. You can choose it by assigning a value like [v15-compat] submissions://[user[:password]@]server[:port]. Because of the SMTPS problems above, and because SUBMISSIONS is new and a successor that lives on the same port as the historical engineering mismanagement named SMTPS, it's usually necessary to explicitly specify the port as :465 ๐Ÿฅฒ.

mta-aliases

[Option] If this is set to a path pointing to a text file in valid MTA (Postfix) aliases(5) format, the file gets loaded and cached (manageable with mtaaliases) ๐Ÿ“‡. From then on, plain name receiver names (see expandaddr) get recursively expanded as a last expansion step, after the distribution lists that can be created with alias ๐Ÿงฉ. There are some constraints on aliases(5) content. Only local addresses (names) that are valid usernames ([a-z_][a-z0-9_-]*[$]?) are treated as expandable aliases. [v15 behaviour may differ] :include:/file/name directives are not supported ๐Ÿ™…โ€โ™€๏ธ. By including -name in expandaddr, you can assert that only expanded names (mail addresses) are passed through to the MTA ๐Ÿซก.

mta-arguments

These are arguments to pass through to a file-based mta (Mail-Transfer-Agent) ๐Ÿ“ฆ. They're parsed according to โ€œShell-style argument quotingโ€ into an array of arguments, which gets joined onto MTA options from other sources. For example: ? wysh set mta-arguments='-t -X "/tmp/my log"' ๐Ÿ’–.

mta-no-default-arguments

(Boolean) This one avoids passing standard command line options to a file-based mta (please see there) ๐Ÿ™ˆโœจ.

mta-no-receiver-arguments (Boolean)

okay so by default, every receiver address gets passed as a command line option to a file-based mta ๐Ÿ“ฎ. Setting this variable turns that off, which helps MTAs that give those arguments special treatment ๐Ÿซถ. Just know that turning it off can mean you need to pass a -t through mta-arguments so the MTA knows to use the passed message as a template โœจ.

mta-argv0

Lots of systems use a so-called mailwrapper(8) setup to stay compatible with sendmail(1) ๐Ÿงš. It works by checking the name that was used to invoke the mail delivery system. If this variable is set, the mailwrapper (the program that actually runs when you call the file-based mta) will treat its contents as that name ๐Ÿ’….

mta-bcc-ok (Boolean)

this one is a little messy ๐Ÿ˜ญ. In violation of RFC 5322, some MTAs don't remove Bcc: header lines from transported messages after they've noted the receivers. (Exim and Courier, for example, need the command line option -t to actually enforce removal ๐Ÿ™„.) Unless this is set, and the corresponding receivers are addressed only by protocol-specific means or MTA command line options, the header itself gets stripped before the message is sent over the wire ๐Ÿฆ‹.

netrc-lookup-USER@HOST, netrc-lookup-HOST, netrc-lookup

(Boolean)[v15-compat][Option] This one controls whether your ~/.netrc file gets used to look up account credentials ๐Ÿ”‘. It's covered in the section โ€œOn URL syntax and credential lookupโ€ and for the command netrc. The section โ€œThe .netrc fileโ€ documents the file format if you want the deets. Also see netrc-pipe.

netrc-pipe

[v15-compat][Option] Okay this is so clever ๐Ÿง . When ~/.netrc is loaded (see netrc and netrc-lookup), S-nail will read the output of a shell pipe instead of your ~/.netrc file, but only if this is set to the desired shell command. This is how you could, for example, store your ~/.netrc in encrypted form ๐Ÿ”:

? set netrc-pipe='gpg -qd ~/.netrc.pgp'

newfolders

[Option] If this is set to maildir, newly created local folders will be Maildir instead of MBOX ๐Ÿ“.

newmail

Checks for new mail in the current folder every time the prompt shows up ๐Ÿ‘€. A Maildir folder has to be re-scanned to figure out if new mail has arrived. If you set this to the special value nopoll, a Maildir folder won't get rescanned completely. Instead, only timestamp changes get detected. Maildir folders are [Option]al ๐ŸŒธ.

outfolder (Boolean)

When this is on, a non-absolute filename given to record, plus the sender-based filenames used by the Copy, Save, Followup and followup commands, get interpreted relative to the folder directory instead of the current directory ๐Ÿ“‚.

on-account-cleanup-ACCOUNT, on-account-cleanup

This is a macro hook that runs once you leave an account, as the very last step before the per-account localopts get unrolled ๐Ÿงน. It runs even if there's a fatal error, including ones caused by switching to the account in the first place ๐Ÿ’€. Keep it to only the absolutely necessary stuff, like cleaning up alternates, for example. The specialized form wins over the generic one if it's found ๐Ÿ‘‘.

on-compose-cleanup

This is a macro hook that gets called after the message has been sent (or not, if something failed) ๐Ÿ“จ. It's the very last step before the compose mode localopts get unrolled. It runs even on fatal errors, so again, only the absolutely necessary actions, like cleaning up alternates, for example ๐Ÿซถ.

For compose mode hooks that might affect the message content, check out on-compose-enter, on-compose-leave, and on-compose-splice.

[v15 behaviour may differ] This hook exists because things like alias, alternates, commandalias, and shortcut (to name a few) aren't covered by localopts or by local:. That means changes made in compose mode keep sticking around afterwards ๐Ÿ˜ค.

on-compose-enter, on-compose-leave

These are macro hooks that get called once compose mode is entered, and after composing has been finished, respectively ๐Ÿ’Œ. The exact order of steps is documented for ~., which is one of the โ€œCOMMAND ESCAPESโ€ ๐Ÿ“œ. You can query context about the message you're working on via digmsg. localopts are enabled for these hooks, and any variable changes get forgotten after the message has been sent. Use on-compose-cleanup for any other cleanup steps ๐Ÿงฝ.

Here's an example that injects a signature via message-inject-tail. Honestly, using on-compose-splice to simply inject the file you want via ~< or ~<! might be a better approach ๐Ÿ’….

define t_ocl {
  vput ! i cat ~/.mysig
  if $? -eq 0
     vput csop message-inject-tail trim-end $i
  end

  # Alternatively
  readctl create ~/.mysig
  if $? -eq 0
    readall i
    if $? -eq 0
      vput csop message-inject-tail trim-end $i
    end
    readctl remove ~/.mysig
  end
}
set on-compose-leave=t_ocl

on-compose-splice, on-compose-splice-shell

These hooks run once the normal compose mode is finished, but before the on-compose-leave macro hook and friends get called ๐ŸŽฌ. Both hooks run in a subprocess, with their input and output connected to S-nail so they can act as if they were an interactive user ๐Ÿค–. The difference between them is that the latter is a SHELL command, whereas the former is a normal defined macro. The macro is restricted to a small set of commands, though (the verbose output of, for example, list will indicate said capability). localopts are enabled for these hooks (in the parent process), which means any setting gets forgotten after the message has been sent. Use on-compose-cleanup for any other cleanup as necessary ๐Ÿงน.

During these hooks, S-nail temporarily forgets whether it has been started in interactive mode ๐Ÿ™ˆ. A restricted set of โ€œCOMMAND ESCAPESโ€ will always be available, and for guaranteed reproducibility, escape and ifs will be set to their defaults. The compose mode command ~^ has been especially designed for scriptability via these hooks ๐Ÿงš. The first line the hook reads on its standard input is the protocol version of said command escape, currently 0 0 2.

Backward incompatible protocol changes have to be expected, so keep an eye on the version ๐Ÿ‘€.
Be careful with deadlocks! Avoid false control flow: if both involved processes wait for more input at the same time, or one doesn't expect more input while the other is stuck waiting to consume its output, etc., things will hang ๐Ÿ’€.

There's no automatic synchronization of the hook. It won't be stopped automatically just because it, e.g., emits ~x. The hooks will receive a termination signal if the parent enters an error condition. [v15 behaviour may differ] Protection against and interaction with signals is not yet given. It's likely that in the future these scripts will be placed in an isolated session, which gets signalled in its entirety as necessary ๐Ÿฆ‹.

Here's the macro version of the splice hook:

define ocs_signature {
  read version
  echo '~< ~/.mysig' # '~<! fortune pathtofortunefile'
}
set on-compose-splice=ocs_signature

And here's the shell version, which uses wysh to set it:

wysh set on-compose-splice-shell=$'\
  read version;\
  printf "hello $version!  Headers: ";\
  echo \'~^header list\';\
  read status result;\
  echo "status=$status result=$result";\
  '

And here's a more involved macro that reads the header list and inserts a Cc: header if needed ๐Ÿ’–:

define ocsm {
  read version
  echo Splice protocol version is $version
  echo '~^h l'; read hl; vput csop es subs "${hl}" 0 1
  if "$es" != 2
    echoerr 'Cannot read header list'; echo '~x'; xit
  endif
  if "$hl" !%?case ' cc'
    echo '~^h i cc "Diet is your <mirr.or>"'; read es;\
      vput csop es substring "${es}" 0 1
    if "$es" != 2
      echoerr 'Cannot insert Cc: header'; echo '~x'
      # (no xit, macro finishes anyway)
    endif
  endif
}
set on-compose-splice=ocsm

on-history-addition

This hook gets called if an entry is about to be added to the history of the MLE, as documented in โ€œOn terminal control and line editorโ€ ๐Ÿ“š. It's called with three arguments. The first is the name of the input context (see bind). The second is either an empty string or the matching history-gabby type. The third is the complete command line that's about to be added. The entry won't be added to history if the hook returns non-0 ๐Ÿšซ.

[v15 behaviour may differ] A future version will give the expanded command name as the third argument, followed by the tokenized command line as parsed in the remaining arguments. The first of those is the original unexpanded command name. So one may do shift 4 and will then be able to access the positional parameters as usual via *, #, 1 etc. ๐Ÿ”ฎ

on-main-loop-tick

This hook gets called whenever the program's main event loop is about to read the next input line ๐Ÿ”.

Heads up! Variable and other changes it performs are not scoped as via localopts! ๐Ÿ™ˆ

on-program-exit ๐Ÿšชโœจ this hook fires when the program exits, whether you bail with exit or quit, or because send mode is finished. โš ๏ธ heads up, it runs super late, so the terminal settings and stuff are already torn down by then ๐Ÿ˜ญ

  • on-resend-cleanup ๐Ÿงน [v15 behaviour may differ] basically the same as on-compose-cleanup, except it only goes off when you resend. ๐Ÿ’…
  • on-resend-enter ๐ŸŽ€ [v15 behaviour may differ] same energy as on-compose-enter, but again, only triggered by resend. Right now there's no digmsg support for it, for example. ๐Ÿ™ˆ
  • page ๐Ÿ“„ (Boolean) if this is set, every message fed through the pipe command gets a formfeed character \f after it. Very organized of you ๐ŸŒธ
  • password-USER@HOST, password-HOST, password ๐Ÿ” [v15-compat] this is a variable chain that sets a password. It only gets used when there isn't one already given in the protocol or the account-specific URL. As a last resort, S-nail will ask you for the password on your own terminal if the auth method needs one. โš ๏ธ Storing passwords in a startup file is generally a security risk, so that file should only be readable by the user who's running the thing. Protect your secrets, bestie ๐Ÿซถ
  • password-USER@HOST ๐Ÿ—๏ธ [no v15-compat] (see the chain above for [v15-compat]) this sets the password for USER when connecting to HOST. If there's no variable defined for that host, you'll get asked for the password on standard input. Same warning applies: passwords in a startup file are a security risk, so keep the file readable by the invoking user only. ๐Ÿ˜ค
  • piperaw ๐Ÿฆ‹ (Boolean) this sends messages to the pipe command without doing any MIME or character set conversions. Raw and unfiltered, iconic ๐Ÿ’–
  • pipe-EXTENSION ๐Ÿงฉ this is identical to pipe-TYPE/SUBTYPE, except EXTENSION (normalized to lowercase using the ASCII charset's character mappings) is a file extension instead, like xhtml. Handlers registered this way take precedence, so they're the main character here ๐Ÿ‘‘
  • pipe-TYPE/SUBTYPE ๐ŸŽž๏ธ okay so, when a MIME message part is identified as TYPE/SUBTYPE (case-insensitive, normalized to lowercase using ASCII character mappings), it gets displayed or quoted, and its text is filtered through this variable's value, which gets interpreted as a shell command. Unless noted otherwise, only parts you can display as inline plain text (see copiousoutput) are covered. Every other MIME part only gets considered by and for mimeview. ๐ŸŒˆ

The special value question mark ? forces the message part to be interpreted as plain text, like set pipe-application/xml=?. (You can also get this by adding a MIME type-marker via mimetype.) [Option]ally, MIME type handlers can be defined in "The Mailcap files", which is where you should look for docs on flags like copiousoutput. The question mark is also a trigger character that tells S-nail a set of flags is coming, which adjust how the rest of the value (the shell command) behaves. For example:

? set pipe-X/Y='?!++=? vim ${MAILX_FILENAME_TEMPORARY}'
  • * โ™ป๏ธ the command output can be reintegrated into this MUA's normal processing: copiousoutput. Implied when using a plain โ€˜โ€™.
  • # ๐Ÿ‘€ only use this handler for display, not for quoting a message: x-mailx-noquote.
  • & ๐Ÿƒโ€โ™€๏ธ run the command asynchronously and don't wait for the handler to exit: x-mailx-async. The standard output of the command goes to /dev/null.
  • ! ๐Ÿ–ฅ๏ธ the command has to run on an interactive terminal, and the terminal gets temporarily released so it can run: needsterminal.
  • + ๐Ÿ“ requests creation of a zero-sized temporary file. Its absolute pathname is made accessible through the environment variable MAILX_FILENAME_TEMPORARY: x-mailx-tmpfile. If you give it twice, S-nail unlinks the file automatically when the command loop is entered again at the latest: x-mailx-tmpfile-unlink. โš ๏ธ It's an error to use automatic deletion together with x-mailx-async, so don't mix those up ๐Ÿ’€
  • = โœ๏ธ normally the MIME part content gets passed to the handler through standard input, but with this flag the data is written into MAILX_FILENAME_TEMPORARY instead (x-mailx-tmpfile-fill). Creating that file is implied. To get the temporary file automatically deleted, you still need two plus signs ++.
  • t ๐Ÿ“ text type-marker: displays this as normal plain text (see "The mime.types files" for type-markers). It's identical to just giving a plain ?, and it implies copiousoutput.
  • h ๐ŸŒ [Option] HTML type-marker: displays via the built-in HTML-to-text filter. Implies copiousoutput.
  • ? ๐Ÿ›‘ another question mark forcefully ends the interpretation of the remaining characters, so there's no confusion with normal shell command content. (Any character that isn't in this list has the same effect, fr fr.)

Some info about the MIME part being displayed gets embedded into the environment of the shell command:

  • MAILX_CONTENT ๐Ÿท๏ธ the MIME content-type of the part, if it's known. Otherwise it's just the empty string.
  • MAILX_CONTENT_EVIDENCE ๐Ÿ” if mime-counter-evidence includes the carry-around-bit (2), this gets set to the detected MIME content-type. Otherwise it's only identical to MAILX_CONTENT, not always.
  • MAILX_EXTERNAL_BODY_URL ๐Ÿ”— MIME parts of type message/external-body access-type=url store their access URL in this variable. It's empty otherwise. โš ๏ธ URL targets should not be activated automatically, without supervision, so don't just click stuff blindly ๐Ÿ˜ญ
  • MAILX_FILENAME ๐Ÿ“Ž the filename, if one is set. Otherwise it's the empty string.
  • MAILX_FILENAME_GENERATED ๐ŸŽฒ a random string.
  • MAILX_FILENAME_TEMPORARY ๐Ÿ—‚๏ธ if temporary file creation was requested through the command prefix, this variable gets set and contains the absolute pathname of the temporary file.
  • pop3-auth-USER@HOST, pop3-auth-HOST, pop3-auth ๐Ÿ”‘ [Option][v15-compat] this variable chain sets the POP3 authentication method. The supported ones are the default plain, [v15-compat] oauthbearer (see the "FAQ" entry "But, how about XOAUTH2 / OAUTHBEARER?"), and also [v15-compat] external and externanon, which are for TLS secured connections that pass a client certificate via tls-config-pairs. There may also be the [Option]al method [v15-compat] gssapi. externanon doesn't need any user credentials at all. external and gssapi need a user, and the remaining ones also require a password. โœจ externanon only builds on the credentials passed through a client certificate, and it's usually the way to go, since tested servers don't actually follow RFC 4422 and fail if you pass extra credentials. Unless pop3-no-apop is set, the plain method will [Option]ally get replaced with APOP if that's possible (see there). ๐Ÿงš
  • pop3-bulk-load-USER@HOST, pop3-bulk-load-HOST, pop3-bulk-load ๐Ÿ“ฆ (Boolean)[Option] when S-nail accesses a POP3 server, it loads the headers of the messages and only requests the message bodies when you ask for them. For POP3 that means the message headers get downloaded twice ๐Ÿ˜ฉ If this variable is set, S-nail downloads only complete messages from the given POP3 server(s) instead. Efficient bestie behavior ๐Ÿ’œ
  • pop3-keepalive-USER@HOST, pop3-keepalive-HOST, pop3-keepalive โฐ [Option] POP3 servers close the connection after a period of inactivity. The standard says this has to be at least 10 minutes, but practical experience may vary, so don't trust it blindly ๐Ÿ™ˆ Setting this variable to a numeric value greater than 0 makes S-nail send a NOOP command every value seconds if no other operation is performed. Keeping the vibe alive ๐Ÿซถ
  • pop3-no-apop-USER@HOST, pop3-no-apop-HOST, pop3-no-apop ๐Ÿซฃ (Boolean)[Option] unless this variable is set, the MD5 based APOP authentication method gets used instead of your chosen plain pop3-auth when you connect to a POP3 server that advertises support for it. The advantage of APOP is that only a single packet gets sent for the user/password tuple. (Originally it was also because the password isn't sent in clear text over the wire, but MD5 doesn't offer sufficient security anymore, and today transport is almost always TLS secured.) Note that pop3-no-apop-HOST requires [v15-compat].
  • pop3-use-starttls-USER@HOST, pop3-use-starttls-HOST, pop3-use-starttls ๐Ÿ”’ (Boolean)[Option] this causes S-nail to issue a STLS command, which makes an unencrypted POP3 session TLS encrypted. Not every server supports this, and it isn't used if the session is already encrypted by the POP3S method. Note that pop3-use-starttls-HOST requires [v15-compat].
  • posix ๐ŸŒท (Boolean) this flag enables POSIX mode, which changes the behaviour of S-nail wherever it deviates from standardized behaviour. It's automatically squared with the environment variable POSIXLY_CORRECT, so changing one will adjust the other, periodt ๐Ÿ’… The following behaviour is covered and enforced by this mechanism:
  • Non-interactive mode ๐Ÿฅบ means any error while loading resource files at startup makes the program exit. In interactive mode, the same errors only stop loading of the currently loaded (stack of) file(s, i.e., recursively). You can dodge these exits on a per-command basis with ignerr, one of the โ€œCommand modifiersโ€ ๐Ÿฆ‹, on each command that's allowed to fail.
  • alternates ๐Ÿ’œ replaces the list of alternate addresses instead of adding to it. It's only honoured for message replies of any sort, and for aliases.
  • The variable-inserting โ€œCOMMAND ESCAPESโ€ ~A, ~a, ~I and ~i will expand the embedded character sequences \t (horizontal tab) and \n (line feed) โœจ. [v15 behaviour may differ] For compatibility reasons this step always runs, no exceptions.
  • Reading in messages via ~f (โ€œCOMMAND ESCAPESโ€) uses the type headerpick selection, not forward ๐ŸŒธ.
  • When you switch the active folder, no header summary gets shown, even if header is set ๐Ÿ™ˆ.
  • Setting ignoreeof implies the behaviour described by dot ๐Ÿซถ.
  • The keep variable now covers any empty mailbox, not just empty โ€œprimary system mailboxโ€es. Empty mailboxes get removed when they're left that way ๐Ÿงน.
  • Each command has an exit ? and error ! status that overwrites the status of the last command. In POSIX mode the program exit status signals failure regardless, unless every message went out to the mta successfully. Also check out sendwait ๐Ÿ‘‘.

(Boolean) Okay so, when a MIME message part of type multipart/alternative gets displayed and has a subpart of type text/plain, the other parts usually get tossed ๐Ÿ—‘๏ธ. Setting this variable makes every subpart show up, just like the surrounding part was multipart/mixed ๐ŸŒˆ.

๐ŸŽ€ prompt

This is the string used as your prompt in interactive mode ๐Ÿ’…. Every time the variable gets evaluated, its value is treated as if it were wrapped in dollar-single-quotes (see โ€œShell-style argument quotingโ€). That second expansion is what lets you embed status info, like ?, !, account or mailbox-display โœจ.

Want characters to be ignored when the visual width of the result is calculated? Wrap them in a pair of reverse solidus escaped brackets: \[\E[0m\]. There's also a slot for coloured prompts through the [Option]al command colour ๐ŸŒˆ. And if you don't want any prompting at all, set this to the null string (aka set noprompt) ๐Ÿ™ˆ.

๐Ÿฆ‹ prompt2

Secondary prompts use this string. Otherwise it's identical to prompt. The default is .. ๐Ÿงš.

๐ŸŒธ quiet

(Boolean) Suppresses printing the version when the program is first invoked ๐Ÿคซ.

๐Ÿ’… quote

If this is set, messages processed by the variants of followup and reply start with the original message ๐Ÿ’ฌ. Lines of it get prefixed by indentprefix, taking quote-chars and quote-fold into account ๐ŸŽ€.

No headers get quoted when it's set without a value, or set to noheading. Set to headers, the type headerpick selection gets included in the quote. allbodies embeds the (body) contents of all MIME parts, and allheaders also includes every header ๐Ÿ“œ. The quoted message gets wrapped by the expansions of quote-inject-head and quote-inject-tail ๐Ÿซถ.

Also check out quote-add-cc, quote-as-attachment and ~Q, one of the โ€œCOMMAND ESCAPESโ€ ๐Ÿ”ฅ.

๐Ÿ’Œ quote-add-cc

(Boolean) Decides whether the senders of messages quoted via ~Q become members of the carbon copies Cc: list ๐Ÿ’Œ.

๐Ÿ“Ž quote-as-attachment

(Boolean) When you reply to a message, this adds the original message in its entirety as a message/rfc822 MIME attachment ๐Ÿ“Ž. It works regardless of how quote is set ๐Ÿ’–.

๐Ÿ“ quote-chars

This can be set to a string made of non-whitespace ASCII characters, which get treated as quotation leaders. The default is >|}: ๐Ÿ“.

โœจ quote-fold

[Option] This can be set alongside indentprefix for a fancier quotation ๐Ÿ’ซ. Leading quotation characters (quote-chars) get compressed, and overlong lines get folded โœ‚๏ธ.

quote-fold takes one, two or three space-separated numeric values. They're read as the maximum (goal) and the minimum line length, respectively. It's kind of like the fmt(1) program, but line-based instead of paragraph-based ๐Ÿง . The third value is used as the maximum line length instead of the first when no better break point can be found. It's ignored unless it's larger than the minimum and smaller than the maximum ๐Ÿ™ƒ. If you don't set the minimum explicitly, it reflects the goal algorithmically ๐Ÿงฎ.

The goal can't be smaller than the length of indentprefix plus some additional pad. Necessary adjustments happen silently, so don't be surprised ๐Ÿคซ.

๐Ÿฆ‹ quote-inject-head, quote-inject-tail

These are the strings that go before and after the text of a quoted message, if they're non-empty, respectively ๐Ÿฆ‹. The former defaults to %f wrote:\n\n ๐Ÿ’Œ. Special format directives get expanded if possible, and if so configured, the output gets folded according to quote-fold โœจ.

Format specifiers in these strings start with a percent sign % and expand values from the original message, unless noted otherwise ๐ŸŒˆ. Names and addresses are not subject to the setting of showto, so keep that in mind ๐Ÿ’€. The valid format specifiers are:

  • %% โ€” A plain percent sign ๐Ÿ’….
  • %a โ€” The address(es) of the sender(s) ๐Ÿ“ฌ.
  • %d โ€” The date found in the Date: header of the message when datefield is set (the default). Otherwise it's the date the message was received ๐Ÿ“…. Formatting can be controlled by assigning a strftime(3) format string to datefield (and datefield-markout-older) ๐Ÿง.
  • %f โ€” The full name(s), name and address as given, of the sender(s) ๐ŸŒธ.
  • %i โ€” The Message-ID: ๐Ÿ†”.
  • %n โ€” The real name(s) of the sender(s) if there's one and showname allows usage. Otherwise, the address(es) ๐ŸŽ€.
  • %r โ€” The sender's real name(s) if there's one, otherwise the address(es) ๐Ÿงš.

๐ŸŒ™ r-option-implicit

(Boolean) Setting this evaluates the contents of from (or, if that contains multiple addresses, sender). It then passes the results onto the used (file-based) MTA, just like the -r option does in the empty-argument case ๐Ÿš€.

๐Ÿ’œ recipients-in-cc

(Boolean) When you reply, the original From: and To: get merged into the new To: by default ๐Ÿซ . So do addressees who may have come in via Reply-To: and Mail-Followup-To: ๐Ÿ’ซ. If this variable is set, a sensitive algorithm tries to put only the sender of the message you're replying to in To:. Everyone else goes into Cc: ๐Ÿ‘‘.

๐Ÿงพ record

Unless this variable is defined, no copies of outgoing mail get saved ๐Ÿ™…โ€โ™€๏ธ. If it is defined, it gives the pathname of a folder where all new, replied-to or forwarded messages get saved ๐Ÿ’พ. The pathname is subject to the usual โ€œFilename transformationsโ€ ๐Ÿ”ฎ.

When saving to this folder fails, the message doesn't get sent. Instead, it's saved to DEAD ๐Ÿ’€.

The standard says relative (fully expanded) paths are interpreted relative to the current directory (cwd) ๐Ÿ—บ๏ธ. To force interpretation relative to the folder outfolder, you need to set that in addition ๐Ÿซถ.

๐ŸŽ record-files

(Boolean) If this variable is set, the meaning of record gets extended to cover messages that only target file and pipe recipients (see expandaddr) ๐Ÿ“. These address types won't appear in recipient lists unless add-file-recipients is also set ๐Ÿ™ˆ.

๐Ÿ” record-resent

(Boolean) If this variable is set, the meaning of record also covers the resend and Resend commands ๐Ÿ“ฎ.

๐ŸŒ reply-in-same-charset

(Boolean) If this variable is set, S-nail first tries to use the same character set as the original message for replies ๐ŸŒ. If that fails, the mechanism described in โ€œCharacter setsโ€ is evaluated as usual ๐Ÿ”„.

๐Ÿ‡ฉ๐Ÿ‡ช reply-strings

This can be set to a comma-separated list of strings, matched case-insensitively according to ASCII rules ๐Ÿ”ค. They get recognised in addition to the built-in strings as Subject: reply message indicators ๐Ÿง . The built-ins are Re:, which is mandated by RFC 5322, plus the German Aw:, Antw:, and the Wg: which has often been seen in the wild ๐ŸŒท. In other words, the separating colon has to be specified explicitly ๐Ÿ’….

๐Ÿ“ฌ reply-to

A list of addresses to put into the Reply-To: field of the message header ๐Ÿ’Œ. Members of this list are handled as if they were in the alternates list ๐Ÿ’œ.

๐Ÿ•ฐ๏ธ replyto

[Obsolete] This is a variant of reply-to ๐Ÿฅ€.

๐Ÿ™‹โ€โ™€๏ธ reply-to-honour

This controls whether a Reply-To: header is honoured when replying to a message via reply or Lreply ๐Ÿซถ. It's a โ€œquadoptionโ€. If it's set without a value, it defaults to โ€œyesโ€ โœ….

๐Ÿ’– reply-to-swap-in ๐Ÿ’–

okay so mailing lists have been SO messy since DKIM and DMARC showed up ๐Ÿ˜ญ A lot of them now rewrite the sender as Name via List <list@address> and stick the original sender in Reply-To:. ๐Ÿ™ˆ If this is set and a Reply-To: exists and it has only one addressee (!), S-nail uses that address in place of the pretended sender ๐Ÿฆ‹ This works independently from reply-to-honour. The optional value is a comma-separated list of strings for finer control over when swapping happens. For now the only one supported is mlist, which swaps only when the sender is a mailing list as defined by mlist ๐Ÿ’…

rfc822-body-from_

(Boolean) โœจ Use this to force a so-called From_ line for messages embedded in an envelope mail through the message/rfc822 MIME mechanism. It's just for visual convenience ๐ŸŒธ See also mbox-rfc4155.

save

(Boolean) ๐Ÿ’พ Turns on saving of (partial) messages in DEAD when you hit an interrupt or a delivery error. Lifesaver energy ๐Ÿซถ

screen

The number of lines that counts as one "screenful" ๐Ÿ“บ It's used for the headers summary display, searching, the message topline display, and scrolling with z. If this isn't set, S-nail works it out from the detected terminal window size and the baud rate, so the faster the terminal, the more gets shown ๐Ÿš€ Overall screen dimensions and pager usage are also influenced by the environment variables COLUMNS and LINES, plus the variable crt.

searchheaders

(Boolean) ๐Ÿ” Expands message list specifiers written as /x:y into every message containing the substring "y" in the header field x. The search is case insensitive, so no stress ๐Ÿ’œ

sendcharsets

[Option] ๐ŸŒˆ A comma-separated list of character set names that can be used in outgoing internet mail. The value of charset-8bit gets automatically tacked onto the end of this list. If no character set conversion capabilities are compiled into S-nail, then the only supported charset is ttycharset. Also see sendcharsets-else-ttycharset, and check out the section "Character sets" for the whole picture of character set conversion in S-nail ๐ŸŽ€

sendcharsets-else-ttycharset

(Boolean)[Option] ๐Ÿงš If this is set but sendcharsets isn't, S-nail acts like sendcharsets was set to the value of ttycharset. Basically this passes your message data through in the character set of your current locale encoding. So mail text is (assumed to be) ISO-8859-1 when you send from an ISO-8859-1 locale, and UTF-8 when you send from a UTF-8 locale โœจ

The 8-bit fallback charset-8bit never comes into play, because ttycharset is implicitly assumed to be 8-bit and able to represent all files the user may specify. (This is also the case when there's no character set conversion support in S-nail, and ttycharset is the only supported set. See "Character sets".) ๐Ÿ™ˆ This might be a problem for scripts that use the suggested LC_ALL=C setting, since US-ASCII is the character set by definition in that case. So it's better to override ttycharset as well, and/or do something like this in the resource file:

# Avoid ASCII "propagates to 8-bit" when scripting
\if ! t && "$LC_ALL" != C && "$LC_CTYPE" != C
  \set sendcharsets-else-ttycharset
\end

sender

An address that gets put into the Sender: field of outgoing messages ๐Ÿ“ฎ Per RFC 5322, this is the mailbox of the agent responsible for actually transmitting the message. It should normally not be used unless the from field has more than one address, and in that case it's required ๐Ÿซถ > [v15 behaviour may differ] Expect automatic management of the from and sender relationship. Depending on the context, this address gets handled as if it were in the list of alternates. Also see -r and r-option-implicit ๐Ÿ’…

sendmail

[Obsolete] ๐Ÿฅ€ Predecessor of mta.

sendmail-arguments

[Obsolete] ๐Ÿฅ€ Predecessor of mta-arguments.

sendmail-no-default-arguments

[Obsolete](Boolean) ๐Ÿฅ€ Predecessor of mta-no-default-arguments.

sendmail-progname

[Obsolete] ๐Ÿฅ€ Predecessor of mta-argv0.

sendwait

Sending to the chosen mta or to command-pipe receivers (see "On sending mail, and non-interactive mode") happens asynchronously ๐ŸŒ€ That means you only get to see startup errors from the program, not delivery errors. Also, nobody can guarantee when the program will actually run, or when it'll have produced any output ๐Ÿ˜ญ

If this variable is set, S-nail waits for the child program to exit and uses its exit status code to decide success ๐ŸŽฏ Remarks: this is in conflict with the POSIX standard, because sendwait is built in to be initially set ๐Ÿ’€ Another difference is that it can have a value. That value is a comma-separated list of case-insensitive strings naming specific subsystems for which synchronousness gets ensured (and only those). The possible values are mta for mta delivery, and pcc for command-pipe receivers ๐Ÿ‘‘

showlast

(Boolean) ๐ŸŒธ Makes S-nail start at the last message instead of the first one when you open a mail folder, and also with from and headers.

showname

(Boolean) ๐Ÿงš Makes S-nail show the sender's real name instead of the plain address in the header field summary and in message specifications ๐Ÿ’–

showto

(Boolean) ๐Ÿ‘€ Shows the recipient of the message in the header summary if the message was sent by you ๐Ÿ’œ

Sign

The value backing ~A, one of the "COMMAND ESCAPES" โœ๏ธ Also see message-inject-tail, on-compose-leave and on-compose-splice.

sign

The value backing ~a, one of the "COMMAND ESCAPES" โœ๏ธ Also see message-inject-tail, on-compose-leave and on-compose-splice.

signature

[Obsolete] ๐Ÿฅ€ Please use on-compose-splice or on-compose-splice-shell or on-compose-leave, and (if necessary) message-inject-tail instead! Bye bye, old friend ๐Ÿ˜ญ

skipemptybody

(Boolean) ๐Ÿ™ˆ If an outgoing message has an empty first or only message part, it doesn't get sent. It gets discarded instead, and that counts as success ๐ŸŽ€ Also see the command line option -E.

smime-ca-dir, smime-ca-file

[Option] ๐Ÿ” These specify where your trusted CA certificates live, in PEM (Privacy Enhanced Mail) format, so S/MIME signed messages can be verified ๐Ÿฆ‹ tls-ca-dir documents the prep steps you need for the former. The set of CA certificates built into the TLS library can be turned off by setting smime-ca-no-defaults, and you can fine-tune things further with smime-ca-flags โœจ

smime-ca-flags

[Option] ๐Ÿ”ง Lets you fine-tune how the X509 CA certificate storage and the certificate verification behave. The actual values and what they mean are documented under tls-ca-flags ๐Ÿ’…

smime-ca-no-defaults

(Boolean)[Option] ๐Ÿšซ Stops S-nail from loading the default CA locations built into the TLS library when verifying S/MIME signed messages ๐ŸŒˆ

smime-cipher-USER@HOST, smime-cipher

[Option] ๐Ÿ” Picks the cipher used when generating S/MIME encrypted messages (for the specified account) ๐Ÿ’– RFC 5751 mandates a default of aes128 (AES-128 CBC). The possible values are case-insensitive and, in decreasing cipher strength, are:

  • aes256 โ€” AES-256 CBC
  • aes192 โ€” AES-192 CBC
  • aes128 โ€” AES-128 CBC
  • des3 โ€” DES EDE3 CBC, 168 bits; the default if aes128 is not available
  • des โ€” DES CBC, 56 bits

Which cipher algorithms are actually available depends on the cryptographic library S-nail uses ๐Ÿง  [Option] Support for more cipher algorithms may be available through dynamic loading via EVP_get_cipherbyname(3) (OpenSSL), if S-nail was compiled to support it ๐ŸŒธ

smime-crl-dir

[Option] ๐Ÿ“‚ Specifies a directory containing files with CRLs in PEM format, to use when verifying S/MIME messages ๐Ÿฆ‹

smime-crl-file

[Option] ๐Ÿ“„ Specifies a file containing a CRL in PEM format, to use when verifying S/MIME messages ๐Ÿฆ‹

smime-encrypt-USER@HOST

[Option] ๐Ÿ”’ If this variable is set, messages sent to the given receiver get encrypted before sending. The value must be set to the name of a file containing a certificate in PEM format ๐Ÿ’œ

If a message goes to multiple recipients, each one that has a matching variable set will get their own individually encrypted message. Everyone else keeps receiving plain text, unless the smime-force-encryption variable is set ๐Ÿ‘€ It's recommended to sign encrypted messages too, so also set the smime-sign variable. content-description-smime-message gets inspected for messages that become encrypted ๐Ÿซถ

smime-force-encryption

(Boolean)[Option] ๐Ÿ›‘ Makes S-nail refuse to send unencrypted messages. No plain text allowed, bestie ๐Ÿ”

smime-sign

This is the start of part 35, so I'll pick up right where the S/MIME options leave off.

Wait, let me reconsider. I shouldn't reintroduce the page or repeat headings from earlier parts. The original text for this part begins at smime-sign and runs through spam-interface. I'll rewrite that content in the requested voice, keeping every option, value, variable, pseudo-host, example line, and cross-reference exactly as given.


smime-sign (Boolean) [Option] ๐Ÿ’Œ ok so this one is about S/MIME signing! When it's turned on, S-nail signs your outgoing messages with the user's (from) private key and tucks the user's certificate in as a MIME attachment. โœจ The payoff is that the recipient can check the sender used a valid certificate, that the email addresses in the certificate match the ones in the header, and that nobody messed with the message content ๐Ÿ”’. It doesn't change the message text either, so people can read it like normal ๐Ÿซถ. content-description-smime-signature will be inspected. Also see smime-sign-cert, smime-sign-include-certs and smime-sign-digest ๐Ÿงš.

smime-sign-cert-USER@HOST, smime-sign-cert [Option] Points to a file in PEM format ๐ŸŽ€. For signing and decryption, this file has to hold the user's private key, followed by his certificate ๐Ÿ’–.

For message signing, โ€˜USER@HOSTโ€™ always comes from the value of from (or, if that has multiple addresses, sender) ๐Ÿฆ‹. For encryption, S-nail expects the recipient's public encryption key (certificate) ๐Ÿ”‘. The command certsave can save certificates from signed messages (check the section โ€œSigned and encrypted messages with S/MIMEโ€ for the deets) ๐Ÿ’…. This mode is usually driven by the specialized form ๐ŸŒธ.

When decrypting, the account is figured out from the recipient fields (โ€˜To:โ€™ and โ€˜Cc:โ€™). S-nail looks for addresses that have one of these variables set ๐Ÿ‘€. It always uses the first match, so if the same message went to more than one of your addresses with different encryption keys, decryption might fail ๐Ÿ˜ญ.

Password-encrypted keys work for signing and decryption too ๐Ÿ”. Automated password lookup is possible through the โ€œpseudo-hostsโ€ โ€˜USER@HOST.smime-cert-keyโ€™ for the private key, and โ€˜USER@HOST.smime-cert-certโ€™ for the certificate stored in the same file โœจ. For example, the hypothetical address โ€˜bob@exam.pleโ€™ could be driven with a private key / certificate pair path defined in smime-sign-cert-bob@exam.ple, and the needed passwords would then be looked up as โ€˜bob@exam.ple.smime-cert-keyโ€™ and โ€˜bob@exam.ple.smime-cert-certโ€™ ๐Ÿง. When decrypting, the value of from is tried as a fallback to provide the necessary โ€˜USER@HOSTโ€™ ๐Ÿซง. To include intermediate certificates, use smime-sign-include-certs ๐ŸŒˆ. The possible password sources are documented in โ€œOn URL syntax and credential lookupโ€ ๐Ÿ“š.

smime-sign-digest-USER@HOST, smime-sign-digest [Option] This picks the message digest to use when signing S/MIME messages ๐Ÿ’œ. Remember that for this, โ€˜USER@HOSTโ€™ refers to the variable from (or, if that has multiple addresses, sender) ๐Ÿฉท. Which algorithms you get depends on the cryptographic library, but at least one usable built-in algorithm is guaranteed as a default ๐ŸŒท. If possible, S-nail goes against the standard RFC 5751 and uses โ€˜SHA512โ€™ instead of the mandated โ€˜SHA1โ€™ because of security concerns ๐Ÿ”ฅ. This variable gets ignored by very old (released before 2010) cryptographic libraries that don't offer the needed interface, and it'll be logged if that happens ๐Ÿฅฒ.

S-nail will try to add built-in support for these message digests, and the names are case-insensitive ๐Ÿ“: โ€˜BLAKE2b512โ€™, โ€˜BLAKE2s256โ€™, โ€˜SHA3-512โ€™, โ€˜SHA3-384โ€™, โ€˜SHA3-256โ€™, โ€˜SHA3-224โ€™, plus the widely available โ€˜SHA512โ€™, โ€˜SHA384โ€™, โ€˜SHA256โ€™, โ€˜SHA224โ€™, and the proposed insecure โ€˜SHA1โ€™, and finally โ€˜MD5โ€™ ๐Ÿ™ˆ. More digests may be available through dynamic loading via the OpenSSL function EVP_get_digestbyname(3) ๐ŸŒŸ.

smime-sign-include-certs-USER@HOST, smime-sign-include-certs [Option] If used, this should be a comma-separated list of files, and each one holds a single certificate in PEM format ๐Ÿ“œ. Those certificates get included in the S/MIME message alongside the smime-sign-cert certificate ๐ŸŽ. This is handy for intermediate certificates from the certificate authority, so the receiver's S/MIME software can verify the entire certificate chain ๐Ÿ”—. That chain starts at a local root certificate, goes over the intermediate certificates, and ends at the smime-sign-cert ๐ŸŒฟ. Top level certificates can also be included in the chain, but the receiver's side won't use them for verification ๐Ÿคทโ€โ™€๏ธ.

For these mechanisms, โ€˜USER@HOSTโ€™ refers to the content of the internal variable from (or, if that has multiple addresses, sender) ๐Ÿ’ซ. The pseudo-host โ€˜USER@HOST.smime-include-certsโ€™ handles password lookups for these certificates, if they were given one, so the lookup can be automated through the mechanisms in โ€œOn URL syntax and credential lookupโ€ ๐Ÿค–.

smime-sign-message-digest-USER@HOST, smime-sign-message-digest [Obsolete][Option] Predecessor(s) of smime-sign-digest ๐Ÿ•ฐ๏ธ.

smtp [Obsolete][Option] To use the built-in SMTP transport, specify an SMTP URL in mta ๐Ÿšš. [v15 behaviour may differ] For compatibility reasons, a set smtp is used in preference to mta ๐Ÿ™ƒ.

smtp-auth-USER@HOST, smtp-auth-HOST, smtp-auth [Option] This variable chain controls the SMTP mta authentication method ๐Ÿ”‘. Possible values are โ€˜noneโ€™ ([no v15-compat] default), โ€˜plainโ€™ ([v15-compat] default), โ€˜loginโ€™, [v15-compat] โ€˜oauthbearerโ€™ (see the โ€œFAQโ€ entry โ€œBut, how about XOAUTH2 / OAUTHBEARER?โ€) and [v15-compat] โ€˜externalโ€™ and โ€˜externanonโ€™ for TLS secured connections that pass a client certificate via tls-config-pairs ๐Ÿ›ก๏ธ. There may also be the optional methods โ€˜cram-md5โ€™ and โ€˜gssapiโ€™ ๐Ÿงฉ. โ€˜noneโ€™ and โ€˜externanonโ€™ don't need any user credentials, โ€˜externalโ€™ and โ€˜gssapiโ€™ need a user name, and all other methods need a user name and a password ๐Ÿซ . โ€˜externanonโ€™ builds only on the credentials passed via a client certificate, and it's usually the way to go, since tested servers don't actually follow RFC 4422 aka RFC 4954 and fail if extra credentials are passed ๐Ÿ’€. Also see mta ๐ŸŒธ. Note that smtp-auth-HOST is [v15-compat]. ([no v15-compat] Requires smtp-auth-password and smtp-auth-user. Note for smtp-auth-USER@HOST: it may be overridden depending on the sender address in the variable from ๐Ÿ‘‘.)

smtp-auth-password [Option][no v15-compat] Sets the global fallback password for SMTP authentication ๐Ÿ—๏ธ. If the authentication method needs a password, but neither smtp-auth-password nor a matching smtp-auth-password-USER@HOST can be found, S-nail will ask for a password on the user's terminal ๐Ÿ–ฅ๏ธ.

smtp-auth-password-USER@HOST [no v15-compat] Overrides smtp-auth-password for specific sender addresses, depending on the variable from ๐ŸŽฏ.

smtp-auth-user [Option][no v15-compat] Sets the global fallback user name for SMTP authentication ๐Ÿง‘โ€๐Ÿ’ป. If the authentication method needs a user name, but neither smtp-auth-user nor a matching smtp-auth-user-USER@HOST can be found, S-nail will ask for a user name on the user's terminal ๐Ÿ’ฌ.

smtp-auth-user-USER@HOST [no v15-compat] Overrides smtp-auth-user for specific values of sender addresses, depending on the variable from ๐Ÿฆ„.

smtp-hostname [Option][v15-compat] Normally S-nail uses the variable from to derive the USER@HOST information needed to issue a MAIL FROM:<> SMTP mta command ๐Ÿ“ฎ. Setting smtp-hostname lets it use the โ€˜USERโ€™ from the SMTP account (mta or the user variable chain) and the given โ€˜HOSTโ€™ (the hostname if the empty string is given, or the local hostname as a last resort) ๐Ÿ . This often allows using an address that is itself valid but hosted by a provider other than the one from which the message is sent (in from) ๐ŸŒ. Setting this variable also influences generated โ€˜Message-ID:โ€™ and โ€˜Content-ID:โ€™ header fields ๐Ÿ†”. If the [Option]al IDNA support is available (see idna-disable), variable assignment is aborted when a necessary conversion fails ๐ŸŒ.

smtp-use-starttls-USER@HOST, smtp-use-starttls-HOST, smtp-use-starttls (Boolean)[Option] Makes S-nail issue a STARTTLS command so the SMTP mta session becomes TLS encrypted, i.e., transport layer security gets enabled ๐Ÿ”โœจ.

socket-connect-timeout [Option] A positive number that defines how long to wait for a socket connection to be established before forcing ^ERR-TIMEDOUT โฐ.

socks-proxy-USER@HOST, socks-proxy-HOST, socks-proxy [Option] If set to the URL of a SOCKS5 server, all network activity gets proxied through it ๐Ÿงฆ. The exception is the single DNS name lookup needed to resolve the proxy URL (unnecessary when given an already resolved IP address) ๐Ÿ”. It's automatically squared with the environment variable SOCKS5_PROXY, so changing one adjusts the other ๐Ÿซถ. This example creates a local SOCKS5 proxy on port 10000 that forwards to the machine โ€˜HOSTโ€™ (with identity โ€˜USERโ€™), and actual network traffic happens from there ๐Ÿฆ‹:

      $ ssh -D 10000 USER@HOST
      $ s-nail -Ssocks-proxy=[socks5://]localhost:10000
      # or =localhost:10000; no local DNS: =127.0.0.1:10000

spam-interface [Option] To use any of the spam-related commands (like spamrate), you have to define the desired spam interface by setting this variable ๐Ÿšซ. Check the manual section โ€œHandling spamโ€ for the full picture of spam handling in S-nail ๐Ÿ“–. All or none of the following interfaces may be available ๐Ÿคทโ€โ™€๏ธ:

  • spamc โ€” okay bestie, this one is all about the spamc(1) hookup ๐Ÿ’Œ It's the interaction with spamc(1) from the spamassassin(1) suite (SpamAssassin: http://spamassassin.apache.org) โœจ Unlike the generic filter interface, S-nail adds the correct arguments for each command on its own and has the smarts to parse the program's output ๐Ÿง  If spamc(1) was found in PATH during compilation, a default value for spamc-command gets baked into the S-nail binary ๐ŸŒธ If you need a specific connection type (instead of putting it in a config file), you can use the variable spamc-arguments, like -d server.example.com -p 783 ๐ŸŽ€ You can also set a per-user configuration with spamc-user. Heads up, though: this interface does NOT inspect the is-spam flag of a message for the command spamforget ๐Ÿ™ˆ
  • filter โ€” generic spam filter support through freely configurable hooks ๐Ÿ”ง This one is made for programs like bogofilter(1), and it needs the hooks' exit status to behave, at least for the command spamrate: 0 means spam, 1 means non-spam, 2 means unsure, and any other return value is a hard error ๐Ÿšจ Since the hooks can include shell code snippets, you can intercept the behaviour wherever you need to ๐Ÿ’… The hooks are spamfilter-ham, spamfilter-noham, spamfilter-nospam, spamfilter-rate and spamfilter-spam, and the manual section โ€œHandling spamโ€ has examples for some programs ๐Ÿ“š The process environment of the hooks will have the variable MAILX_FILENAME_GENERATED set ๐Ÿซถ Also, spam score support for spamrate is NOT on unless the optional regular expression support is available AND the spamfilter-rate-scanscore variable is set ๐Ÿ’€
  • spam-maxsize โ€” (Option) Messages that go over this size won't get passed through to the configured spam-interface ๐Ÿ“ฆ If it's unset or 0, the default of 420000 bytes gets used ๐Ÿ“
  • spamc-command โ€” (Option) The path to the spamc(1) program for the spamc spam-interface ๐Ÿ›ฃ๏ธ The path doesn't get expanded, it's used โ€œas isโ€ ๐Ÿ™… A fallback path gets compiled into the S-nail binary if the executable was found during compilation โœจ
  • spamc-arguments โ€” (Option) S-nail handles most arguments for the spamc spam-interface on its own, but sometimes you'll want to set connection-related ones yourself with this variable, like -d server.example.com -p 783 ๐Ÿ”Œ
  • spamc-user โ€” (Option) Set a username for per-user config files for the spamc spam-interface ๐Ÿ‘ค If it's set to the empty string, S-nail uses the name of the current user ๐Ÿฆ‹
  • spamfilter-ham, spamfilter-noham, spamfilter-nospam, spamfilter-rate, spamfilter-spam โ€” (Option) Command and argument hooks for the filter spam-interface ๐Ÿช The manual section โ€œHandling spamโ€ has examples for some programs ๐Ÿ“–
  • spamfilter-rate-scanscore โ€” (Option) The filter spam-interface is generic, so spam scores aren't supported for it by default ๐Ÿ™ƒ BUT if the optional regular expression support is available, setting this variable gets around that restriction ๐Ÿช„ It works like this: first a number (digits) gets parsed, and it must be followed by a semicolon ; and an extended regular expression. Then that regex is used to parse the first output line of the spamfilter-rate hook, and if the evaluation works out, the group matching the number is read as a floating point scan score ๐Ÿ“Š
  • ssl-ca-dir-USER@HOST, ssl-ca-dir-HOST, ssl-ca-dir, ssl-ca-file-USER@HOST, ssl-ca-file-HOST, ssl-ca-file โ€” Obsolete, Option โ€” these are the old-school predecessors of tls-ca-file and tls-ca-dir ๐Ÿ‘ต Go with the newer names ๐Ÿ’œ
  • ssl-ca-flags-USER@HOST, ssl-ca-flags-HOST, ssl-ca-flags โ€” Obsolete, Option โ€” old predecessor of tls-ca-flags ๐Ÿ•ฐ๏ธ
  • ssl-ca-no-defaults-USER@HOST, ssl-ca-no-defaults-HOST, ssl-ca-no-defaults โ€” Obsolete, Boolean, Option โ€” old predecessor of tls-ca-no-defaults ๐Ÿ•ฐ๏ธ
  • ssl-cert-USER@HOST, ssl-cert-HOST, ssl-cert โ€” Obsolete, Option โ€” please use the Certificate slot of tls-config-pairs instead ๐Ÿ“œ
  • ssl-cipher-list-USER@HOST, ssl-cipher-list-HOST, ssl-cipher-list โ€” Obsolete, Option โ€” please use the CipherString slot of tls-config-pairs instead ๐Ÿ”
  • ssl-config-file โ€” Obsolete, Option โ€” old predecessor of tls-config-file ๐Ÿ—‚๏ธ
  • ssl-config-module-USER@HOST, ssl-config-module-HOST, ssl-config-module โ€” Obsolete, Option โ€” old predecessor of tls-config-module ๐Ÿงฉ
  • ssl-config-pairs-USER@HOST, ssl-config-pairs-HOST, ssl-config-pairs โ€” Obsolete, Option โ€” old predecessor of tls-config-pairs ๐Ÿซง
  • ssl-crl-dir, ssl-crl-file โ€” Obsolete, Option โ€” predecessors of tls-crl-dir and tls-crl-file ๐Ÿฅ€
  • ssl-curves-USER@HOST, ssl-curves-HOST, ssl-curves โ€” Obsolete, Option โ€” please use the Curves slot of tls-config-pairs instead ๐ŸŒ™
  • ssl-features โ€” Obsolete, Option, Read-only โ€” old predecessor of tls-features ๐Ÿ‘€
  • ssl-key-USER@HOST, ssl-key-HOST, ssl-key โ€” Obsolete, Option โ€” please use the PrivateKey slot of tls-config-pairs instead ๐Ÿ—๏ธ
  • ssl-method-USER@HOST, ssl-method-HOST, ssl-method โ€” Obsolete, Option โ€” please use the Protocol slot of tls-config-pairs instead ๐Ÿ› ๏ธ
  • ssl-protocol-USER@HOST, ssl-protocol-HOST, ssl-protocol โ€” Obsolete, Option โ€” please use the Protocol slot of tls-config-pairs instead ๐Ÿ› ๏ธ
  • ssl-rand-file โ€” Obsolete, Option โ€” old predecessor of tls-rand-file ๐ŸŽฒ
  • ssl-verify-USER@HOST, ssl-verify-HOST, ssl-verify โ€” Obsolete, Option โ€” old predecessor of tls-verify โœ…
  • stealthmua โ€” if you set this with NO assigned value, it stops S-nail from generating the Message-ID:, Content-ID: and User-Agent: header fields that have obvious references to S-nail ๐Ÿฅท There are two pitfalls, though. First, the message id of outgoing messages isn't known anymore. Second, an expert can still use the rest of the header info to track down the originating mail user agent ๐Ÿ•ต๏ธโ€โ™€๏ธ If you set it to the value noagent, then that Message-ID: and Content-ID: suppression doesn't happen ๐Ÿ™…โ€โ™€๏ธ
  • system-mailrc โ€” (Read-only) This is the compiled-in path of the system-wide initialization file, one of the โ€œResource filesโ€: s-nail.rc ๐Ÿ 

โœจ termcap โœจ

(Option) This is a comma-separated list of Terminal Information Library (libterminfo, -lterminfo) and/or Termcap Access Library (libtermcap, -ltermcap) capabilities that get used to overwrite or define entries ๐Ÿงš See โ€œOn terminal control and line editorโ€, and escape commas with a reverse solidus \ ๐ŸŽ€ Note that this variable only gets queried once at program startup, so it can only be specified in resource files or on the command line ๐Ÿšจ It's always inspected, no matter whether features shows termcap/terminfo library support via ,+termcap, ๐Ÿ’–

String capabilities form cap=value pairs and are expected unless noted otherwise. Numerics have to be written as cap#number, where the number is in normal decimal notation ๐Ÿ”ข Booleans don't have any value. They're just true or false depending on whether they're defined or not, which means S-nail does NOT support undefining an existing boolean ๐Ÿ’€ String capability values get some expansions before they're used: notations like ^LETTER stand for control-LETTER, and \E can be used to specify escape ๐ŸŒˆ (The control notation ^[ could lead to misreadings when a left bracket follows, which it does for the standard CSI sequence, so \E is the clearer pick ๐Ÿ™ˆ) Three letter octal sequences, as in \061, are supported too ๐Ÿ“

To specify that a terminal supports 256-colours, and to define sequences that home the cursor and produce an audible bell, one might write:

? set termcap='Co#256,home=\E[H,bel=^G'

The following terminal capabilities are or may be meaningful for the operation of the built-in line editor or S-nail in general:

  • am ๐Ÿ’… โ€” auto_right_margin, a boolean that tells us whether the right margin needs special treatment. It's related to xenl (see COLUMNS), and it only kicks in when the library backs it up โœจ
  • clear or cl ๐Ÿงน โ€” clear_screen: clears the screen and sends the cursor home. (Simulated via ho plus cd.)
  • colors or Co ๐ŸŒˆ โ€” max_colors, a numeric capability for the maximum number of colours. S-nail doesn't really care about the terminal beyond that, since it always emits ANSI / ISO 6429 escape sequences. Also see colour.
  • cr ๐Ÿฆ‹ โ€” carriage_return: moves to the first column of the current row. The default built-in fallback is \r.
  • cub1 or le ๐ŸŒธ โ€” cursor_left: moves the cursor left one space, non-destructively. The default built-in fallback is \b.
  • cuf1 or nd ๐Ÿ“ โ€” cursor_right: moves the cursor right one space, non-destructively. The default built-in fallback is \E[C, which most terminals use. Less often you'll see \EC and \EOC ๐Ÿ‘€
  • ed or cd ๐Ÿซง โ€” clr_eos: clears the screen.
  • el or ce ๐Ÿงฝ โ€” clr_eol: clears to the end of the line. (Simulated via ch plus repetitions of space characters.)
  • home or ho ๐Ÿ  โ€” cursor_home: homes the cursor.
  • hpa or ch ๐ŸŽฏ โ€” column_address: moves the cursor to the given column parameter in the current row. (Simulated via cr plus nd.)
  • rmcup or te / smcup or ti ๐ŸชŸ โ€” exit_ca_mode and enter_ca_mode, respectively. These exit and enter the alternative screen (ca-mode), which basically turns S-nail into a fullscreen app. You have to switch this on explicitly by setting termcap-ca-mode, so it's not sneaking up on you ๐Ÿซถ
  • smkx or ks / rmkx or ke โŒจ๏ธ โ€” keypad_xmit and keypad_local, respectively. They enable and disable the keypad. It's always enabled when available, because even keyboards without keypads send different key codes for things like cursor keys, and only with it on do we see the codes we actually care about.
  • xenl or xn ๐ŸŒ™ โ€” eat_newline_glitch: a boolean that says whether a newline written in the last column of an auto_right_margin terminal gets ignored. With it, the full terminal width is available even on autowrap terminals. This gets inspected even without ,+termcap, features.

Many more capabilities that describe key-sequences are documented for bind. Go check those out too, bestie ๐Ÿ’–


termcap-ca-mode ๐Ÿ–ฅ๏ธ

[Option] Lets you use the exit_ca_mode and enter_ca_mode termcapabilities to jump into an alternative exclusive screen, the so-called ca-mode. This usually needs special configuration of the PAGER, and it also depends on the value of crt. Heads up: this variable only gets queried once at program startup, so it can only be set in resource files or on the command line โš ๏ธ

termcap-disable ๐Ÿšซ

[Option] Disables any interaction with a terminal control library. If it's set, only some generic fallback built-ins, and possibly the contents of termcap, describe the terminal to S-nail. Same deal as above: it's only queried once at program startup, so it can only be set in resource files or on the command line ๐Ÿ™ˆ

tls-ca-dir-USER@HOST, tls-ca-dir-HOST, tls-ca-dir, tls-ca-file-USER@HOST, tls-ca-file-HOST, tls-ca-file ๐Ÿ”

[Option] These are the directory and the file, respectively, for pools of trusted CA certificates in PEM (Privacy Enhanced Mail) format. They're used to verify TLS server certificates. Using both at once is fine. The file gets loaded the first time it's needed, and the directory lookup gets done fresh as a last resort whenever it's necessary. The CA certificate pool built into the TLS library can be turned off via tls-ca-no-defaults, and you can fine-tune things further with tls-ca-flags. The directory search needs special filename conventions, so check out SSL_CTX_load_verify_locations(3) and verify(1) (or c_rehash(1)) ๐Ÿ“œ

tls-ca-flags-USER@HOST, tls-ca-flags-HOST, tls-ca-flags ๐ŸŽ›๏ธ

[Option] This one lets you fine-tune how the X509 CA certificate storage and the certificate verification behave (also see tls-verify). The value is a comma-separated list of configuration directives, and any whitespace in between gets ignored. The directives map straight onto flags you can pass to X509_STORE_set_flags(3), which are usually defined in a file called openssl/x509_vfy.h. Which ones work depends on your TLS library version. A directive with no mapping just gets ignored (the error log for that is subject to debug) ๐Ÿค“

These directives are understood, and they're case-insensitive:

  • no-alt-chains ๐Ÿ”— โ€” If the initial chain isn't trusted, don't try to build an alternative chain. Setting this makes OpenSSL verification match older OpenSSL versions, from before automatic building and checking of alternative chains existed. Also see trusted-first.
  • no-check-time โฐ โ€” Don't check certificate or CRL validity against the current time.
  • partial-chain ๐Ÿงฉ โ€” By default, partial, incomplete chains that can't be verified all the way up to the chain top (a self-signed root certificate) won't verify. With this flag set, a chain succeeds if at least one signing certificate in it is in any of the configured trusted stores of CA certificates. SSL_CTX_load_verify_locations(3) has some advice on how to manage your own trusted store of CA certificates.
  • strict ๐Ÿšท โ€” Disables workarounds for broken certificates.
  • trusted-first ๐Ÿฅ‡ โ€” Tries building a chain using issuers from the trusted store first, to avoid problems with server-sent legacy intermediate certificates. Newer versions of OpenSSL support alternative chain checking and enable it by default, which gives the same behaviour. Also see no-alt-chains.

tls-ca-no-defaults-USER@HOST, tls-ca-no-defaults-HOST, tls-ca-no-defaults ๐Ÿ™…โ€โ™€๏ธ

(Boolean)[Option] Don't load the default CA locations that are built into the TLS library, which is used to verify TLS server certificates. It's a yes-or-no switch, so keep it simple โœจ

tls-config-file ๐Ÿ“

[Option] If this variable is set, CONF_modules_load_file(3) gets used (if it's announced via ,+modules-load-file, in tls-features) to allow resource-file-based configuration of the TLS library. This happens once the library gets used for the first time, which can even be early during startup. It gets logged with verbose, so turn that on if you're curious ๐Ÿ‘€ If a non-empty value is given, then the given file gets used instead of the TLS library's global default, after performing "Filename transformations". Be careful here: it's an error if the file can't be loaded ๐Ÿšจ The application name will always be passed as s-nail. Some TLS libraries support application-specific configuration via resource files loaded like this. Please see tls-config-module for that.

tls-config-module-USER@HOST, tls-config-module-HOST, tls-config-module ๐Ÿงš

[Option] If file-based application-specific configuration via tls-config-file is available (announced as ,+ctx-config, by tls-features, which means SSL_CTX_config(3) is available), then you can use one central TLS configuration file for all programs, including s-nail. For example:

# Register a configuration section for s-nail
s-nail = mailx_master
# The top configuration section creates a relation
# in between dynamic SSL configuration and an actual
# program specific configuration section
[mailx_master]
ssl_conf = mailx_tls_config
# And that program specific configuration section now
# can map diverse tls-config-module names to sections,
# as in: tls-config-module=account_xy
[mailx_tls_config]
account_xy = mailx_account_xy
account_yz = mailx_account_yz
[mailx_account_xy]
MinProtocol = TLSv1.2
Curves=P-521
[mailx_account_yz]
CipherString = TLSv1.2:!aNULL:!eNULL:
MinProtocol = TLSv1.1
Options = Bugs

๐Ÿ’– tls-config-pairs-USER@HOST, tls-config-pairs-HOST, tls-config-pairs ๐Ÿ’–

[Option] okay so this variable chain gets read as a comma-separated list of directive/value pairs ๐ŸŽ€ Each directive and its value are joined by an equals sign =, and any whitespace around the pair members gets trimmed off โœ‚๏ธ Keys are (usually) case-insensitive ๐ŸŒธ

Here's the tea ๐Ÿซ– when you put these pairs in a tls-config-module section of a tls-config-file, commas , need to be escaped with a reverse solidus \ ๐Ÿ™ˆ But when you're doing it here, it's different: if the equals sign = is preceded by an asterisk *, then "Filename transformations" get performed on the value, and if those fail it's an error ๐Ÿ˜ญ Also, unless tls-features announces proper support (,+conf-ctx,), only the keys listed below are supported. Otherwise the pairs get passed straight through as arguments to the function SSL_CONF_cmd(3) โœจ

  • Certificate โ€” the filename of a TLS client certificate (chain) that some servers need ๐Ÿ“œ If it doesn't work, it falls back to SSL_CTX_use_certificate_chain_file(3). "Filename transformations" happen here too. If PrivateKey isn't set explicitly, it copies this same value ๐Ÿ”‘ Some services also do so-called external authentication once a TLS client certificate got presented successfully during connection setup. (Connecting is authenticating, bestie ๐Ÿ˜ค๐Ÿ”ฅ)
  • CipherString โ€” a list of ciphers for TLS connections, see ciphers(1) ๐Ÿ” By default nothing gets set, so you get a Protocol-specific list of ciphers instead. The protocol standards define which ciphers are acceptable, possibly cramped by whatever TLS library is in use ๐Ÿฅฒ The fallback is SSL_CTX_set_cipher_list(3).
  • Ciphersuites โ€” a list of ciphers for TLSv1.3 connections, see ciphers(1) ๐Ÿฆ‹ These get joined onto the list from CipherString. It's only available if tls-features announces ,+ctx-set-ciphersuites,, and it's applied as necessary via SSL_CTX_set_ciphersuites(3).
  • Curves โ€” a list of supported elliptic curves, if applicable ๐ŸŒˆ By default no curves are set. The fallback is SSL_CTX_set1_curves_list(3), if available.
  • MaxProtocol, MinProtocol โ€” the maximum and minimum supported TLS versions, respectively ๐Ÿ“ถ These are only available if tls-features announces ,+ctx-set-maxmin-proto,, and they're applied as necessary via SSL_CTX_set_max_proto_version(3) and SSL_CTX_set_min_proto_version(3). These fallbacks use an internal parser that understands the strings SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3, and the special value None, which disables the given limit ๐Ÿ’œ
  • Options โ€” various flags to set ๐ŸŽ›๏ธ The fallback is SSL_CTX_set_options(3), and in that case any value other than (exactly) Bugs results in an error ๐Ÿ’€
  • PrivateKey โ€” the filename of the private key, in PEM format, of a TLS client certificate ๐Ÿ—๏ธ If it's unset, the value of Certificate gets used instead. "Filename transformations" are performed here too. The fallback is SSL_CTX_use_PrivateKey_file(3).
  • Protocol โ€” the TLS protocol that gets used ๐Ÿงš If tls-features announces ,+conf-ctx, or ctx-set-maxmin-proto, then using MaxProtocol and MinProtocol is preferable โœจ The fallback is SSL_CTX_set_options(3), driven by an internal parser that understands the strings SSLv3, TLSv1, TLSv1.1, TLSv1.2, TLSv1.3, and the special value ALL. You can give multiple protocols as a comma-separated list, and any whitespace gets ignored. An optional plus sign + prefix enables a protocol, while a hyphen-minus - prefix disables it. So -ALL, TLSv1.2 enables only the TLSv1.2 protocol ๐Ÿ‘‘

๐Ÿ“ tls-crl-dir, tls-crl-file ๐Ÿ“

[Option] these specify a directory (for tls-crl-dir) or a file (for tls-crl-file) respectively, containing a CRL in PEM format ๐Ÿ“ƒ It gets used when verifying TLS server certificates ๐Ÿ”


๐Ÿงฉ tls-features ๐Ÿงฉ

[Option] (Read-only) this expands to a comma-separated list of the TLS library identity and any optional features ๐Ÿซง To make substring matching easier, the string always starts and ends with a comma. The currently supported identities are libressl (LibreSSL), libssl-0x30000 (OpenSSL v3.0.0 series), libssl-0x10100 (OpenSSL v1.1.x series), and libssl-0x10000 (elder OpenSSL series, other clones) ๐Ÿ•ฐ๏ธ Optional features get a plus sign + in front when they're available, and a hyphen-minus - otherwise โœจ

The currently known features are:

  • conf-ctx (tls-config-pairs) ๐ŸŽ€
  • ctx-config (tls-config-module) ๐ŸŒธ
  • ctx-set-ciphersuites (Ciphersuites slot of tls-config-pairs) ๐Ÿฆ‹
  • ctx-set-maxmin-proto (tls-config-pairs) ๐Ÿ“ถ
  • modules-load-file (tls-config-file) ๐Ÿ“‚
  • tls-rand-file (tls-rand-file) ๐ŸŽฒ

๐Ÿ” tls-fingerprint-USER@HOST, tls-fingerprint-HOST, tls-fingerprint ๐Ÿ”

[Option] it's possible to replace the verification of the connection peer certificate against the entire local pool of CAs (for more, see "Encrypted network communication") with a comparison against a precalculated certificate message digest ๐Ÿซถ That's the so-called fingerprint, and it gets specified as the tls-fingerprint-digest you're using. You can calculate this fingerprint with tls fingerprint HOST ๐Ÿง


๐Ÿงฎ tls-fingerprint-digest-USER@HOST, tls-fingerprint-digest-HOST, tls-fingerprint-digest ๐Ÿงฎ

[Option] this is the message digest to use when creating TLS certificate fingerprints ๐Ÿ”ข The defaults, if available, get tried in this order: BLAKE2s256, then SHA256 ๐Ÿ’… For the complete list of digest algorithms, refer to smime-sign-digest ๐Ÿ“š


๐ŸŽฒ tls-rand-file ๐ŸŽฒ

[Option] if tls-features announces ,+tls-rand-file,, then this variable gets queried to find a file with random entropy data ๐ŸŒ€ That data can be used to seed the P(seudo)R(andom)N(umber)G(enerator), see RAND_load_file(3). If this variable is not set or is empty, or if the "Filename transformations" fail, the default filename gets used instead. That default is RAND_file_name(3), which is normally ~/.rnd ๐Ÿ  If seeding the PRNG was successful, RAND_write_file(3) gets called to update the entropy ๐Ÿ’ซ

Remarks: libraries that don't announce this feature seed the PRNG by other means, so this one's not for them ๐Ÿ™ˆ

๐Ÿ”’ tls-verify-USER@HOST, tls-verify-HOST, tls-verify ๐Ÿ”’

[Option] this variable chain sets the action to perform if an error occurs during TLS server certificate validation ๐Ÿšจ Validation happens against the specified or default trust stores (tls-ca-dir, tls-ca-file, or the TLS library's built-in defaults, unless usage is disallowed via tls-ca-no-defaults), and it's fine-tuned via tls-ca-flags. The valid (case-insensitive) values are:

  • strict โ€” fail and close the connection immediately ๐Ÿšซ
  • ask โ€” ask whether to continue, on standard input ๐Ÿค”
  • warn โ€” show a warning and continue โš ๏ธ
  • ignore โ€” do not perform validation ๐Ÿ™ƒ

The default is ask ๐Ÿ’–


๐Ÿ” toplines ๐Ÿ”

If defined, this gives the number of lines of a message to display with the command top ๐Ÿ“จ If it's unset, the first five lines get printed. If it's set to 0, the variable screen gets inspected instead. If the value is negative, then its absolute value gets used for unsigned right shifting (see vexpr) the screen height ๐Ÿงฎ


๐Ÿซง topsqueeze ๐Ÿซง

(Boolean) if set, the top command series strips adjacent empty lines and quotations ๐Ÿงน


๐Ÿ”ค ttycharset ๐Ÿ”ค

The character set of the terminal S-nail operates on ๐Ÿ’ฌ It's also the one and only supported character set that S-nail can use if no character set conversion capabilities were compiled into it. In that case it defaults to ISO-8859-1. Otherwise it defaults to UTF-8 ๐ŸŒท With sufficient locale support, the default gets deduced from the locale environment if that's set (for example LC_CTYPE, see there for more) ๐Ÿ—บ๏ธ Runtime locale changes get reflected by ttycharset, except during the program startup phase, and except when -S was used to freeze the given value ๐ŸงŠ Refer to the section "Character sets" for the complete picture about character sets ๐ŸŒ


๐Ÿ™ˆ typescript-mode ๐Ÿ™ˆ

(Boolean) a special multiplex variable that disables all variables and settings which would interfere with running S-nail in script(1) ๐Ÿงพ It sets colour-disable, line-editor-disable, and (before startup has completed only) termcap-disable ๐Ÿ’… Unsetting it does not restore the former state of the covered settings, so be careful, bestie ๐Ÿ˜ฌ


๐Ÿ›ก๏ธ umask ๐Ÿ›ก๏ธ

For a safe-by-default policy, the process file mode creation mask umask(2) gets set to 0077 on program startup, after the resource files have been loaded, and unless this variable is set ๐Ÿ” Assigning this an empty value leaves the active setting unchanged. Otherwise, the given value becomes the new file mode creation mask ๐ŸŽ€ Child processes inherit the file mode creation mask of their parent ๐Ÿ‘‘


๐Ÿ‘ค user-HOST, user ๐Ÿ‘ค

[v15-compat] this variable chain sets a global fallback user name ๐Ÿซถ It's used when no user name was given in the protocol and account-specific URL. This variable defaults to the name of the user who runs S-nail ๐ŸŒŸ

  • v15-compat ๐Ÿ’… this one is all about staying upward compatible with S-nail version 15.0, so it decides which config options exist and how they get handled ๐ŸŽ€. If you set it to any non-empty value, the command modifier wysh gets implied, which means you get Shell-style argument quoting instead of Old-style argument quoting for every command that supports both โœจ. This manual uses [v15-compat] and [no v15-compat] to mean the new way and the old way of doing things, respectively ๐Ÿฆ‹
  • verbose ๐Ÿงš verbose mode turns on logging of informational context messages. It used to be a Boolean, but now it can be set multiple times (that's what the command line option -v does), or you can give it a numeric value to crank up the verbosity ๐Ÿ”ฅ. Setting it to 0 switches verbosity off and basically counts as unset (almost, ngl ๐Ÿ™ˆ). The maximum number is 3. Also check out debug ๐Ÿ’œ
  • version, version-date, version-hexnum, version-major, version-minor, version-update ๐Ÿ”’ these are all read-only, and they're the S-nail version info squad ๐ŸŒˆ. version is a string with the complete version identification. version-date is the release date in ISO 8601 notation, without the time. version-hexnum is a 32-bit hexadecimal number where the upper 8 bits store the major version, followed by the minor and update version numbers, which each take up 12 bits ๐Ÿ“. The last three variables, version-major, version-minor and version-update, only contain decimal digits: the major, minor and update version numbers. The output of the command version will include all of this ๐Ÿ‘‘
  • writebackedited ๐Ÿ“‚ if this variable is set, messages you modified with the edit or visual commands get written back to the current folder when you quit it. Heads up though, it's only honoured for writable folders in MBOX format ๐Ÿšจ. Also, the editor gets pointed at the raw message content in that case, so neither MIME decoding nor decryption has been performed ๐Ÿ˜ญ. And proper mbox-rfc4155 โ€˜From_โ€™ quoting of newly added or edited content is also left as an exercise for the user ๐Ÿ’€

โœจ ENVIRONMENT โœจ

okay bestie, let's talk about environment variables ๐ŸŒธ the term is basically a vibe check: these are either officially standardized as main-character parts of the process environment, or they're just commonly found hanging out in there ๐Ÿ’… once S-nail starts, it inherits the environment from sh(1). unless I explicitly say otherwise, these variables blend right into the "INTERNAL VARIABLES" world from S-nail's point of view โœจ so you can manage them with set and unset, and S-nail updates the program environment automatically, which newly created child processes then inherit ๐Ÿฆ‹

if you want other environment variables to be just as integrated, you need to import (link) them with the command environ ๐Ÿงš it can also set and unset non-integrated variables from scratch, as long as your system supports it ๐Ÿ’– here's a POSIX shell example where COLUMNS is set for S-nail only, and EDITOR gets exported first so it affects everything else the running shell does ๐ŸŽ€

$ EDITOR="vim -u ${HOME}/.vimrc"
$ export EDITOR
$ COLUMNS=80 s-nail -R

now the list of the girlies:

  • COLUMNS โ€” the user's preferred width in column positions for the terminal screen ๐Ÿ“ it gets checked and used once at startup in interactive or batch (-#) mode, and after that it's actively managed for child processes and the MLE (see "On terminal control and line editor") in interactive mode ๐Ÿ’œ non-interactive mode always uses a compile-time constant as its fallback, which is 80 columns by default. if you're in batch mode and COLUMNS and LINES are both set but not both usable (empty, not a number, or 0) at startup, S-nail will (try to) figure out the real terminal screen size once ๐Ÿ‘€ (normally sh(1) handles these and unsets them for pipe specifications etc.)
  • DEAD โ€” the name of the (mailbox) folder where aborted messages get saved if save is set ๐Ÿ’€ it defaults to ~/dead.letter. if debug is set, no output gets generated; otherwise the file's contents get replaced ๐Ÿ™ˆ except for shell globs, "Filename transformations" (also see folder) get performed here too โœจ
  • EDITOR โ€” the pathname of the text editor for the edit command and ~e (see "COMMAND ESCAPES") โœ๏ธ VISUAL is for the more display-oriented editor ๐Ÿฆ‹
  • HOME โ€” the user's home directory ๐Ÿก this one only gets used when it actually lives in the process environment ๐Ÿ’… if this directory doesn't exist, isn't accessible, or can't be read, S-nail uses the calling user's home directory instead, and it always uses that for the root user ๐Ÿ‘‘ (there's no writability test, so non-privileged users inside read-only jails can still use it, but depending on settings this directory is a default write target for things like DEAD, MBOX and more ๐Ÿซถ)
  • LC_ALL, LC_CTYPE, LANG โ€” [Option] the names of the locale(7) (checked in this lookup order, and/or see setlocale(3)) that decide which "Character sets" are used ๐ŸŒˆ changing these at runtime triggers automatic updates of the whole locale system, which includes updating ttycharset (except during startup, when the variable has been frozen via -S) โœจ
  • LINES โ€” the user's preferred number of lines for the terminal screen ๐Ÿ“ฑ it behaves like COLUMNS, except the compile-time constant used in non-interactive mode and as a fallback defaults to 24 (lines) ๐Ÿ’–
  • LISTER โ€” the pathname of the directory lister used by the folders command when working with local mailboxes ๐Ÿ“‚ the default is ls(1) (path search through SHELL) ๐Ÿ“
  • LOGNAME โ€” at startup S-nail actively makes sure this refers to the name of the user running S-nail, so it can pass a verified name to any newly created child process ๐Ÿซถ
  • MAIL โ€” used as the user's "primary system mailbox" unless inbox is set ๐Ÿ“ฌ if the environmental fallback isn't set either, a built-in compile-time default gets used ๐ŸŽ€ it's assumed to be an absolute pathname ๐Ÿ’…
  • MAILCAPS โ€” [Option] overrides the default path search for "The Mailcap files" ๐Ÿ—‚๏ธ every existing file found gets loaded in sequence, with its contents appended to the list of MIME type handler directives ๐ŸŒธ otherwise, the RFC 1524 standard default gets assigned: ~/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap โœจ (the default value is a compile-time [Option].)
  • MAILRC โ€” used as the startup file instead of ~/.mailrc if it's set ๐Ÿ“ to avoid side effects from configuration files, scripts should either set this to /dev/null or use the -: command line option ๐Ÿ™ˆ
  • MAILX_NO_SYSTEM_RC โ€” if this is set, reading s-nail.rc (aka system-mailrc) at startup gets blocked ๐Ÿšซ the same effect happens as if S-nail was started with the option -: (and its argument) or -n ๐Ÿ’… this variable is only used when it lives in the process environment ๐Ÿซถ
  • MBOX โ€” the name of the user's "secondary mailbox" file ๐Ÿ“ฆ a logical subset of the special "Filename transformations" (also see folder) is supported ๐Ÿฆ‹ the default is ~/mbox ๐Ÿ“ traditionally, MBOX is the file where read messages from the "primary system mailbox" get saved ๐Ÿ’œ also see "Message states" โœจ
  • NETRC โ€” [v15-compat][Option] overrides the default location of the user's ~/.netrc file ๐Ÿ”‘
  • PAGER โ€” the pathname of the program used for backing the more command, and for when the crt variable forces a pager for output ๐Ÿ“– the default paginator is more(1) (path search through SHELL) ๐ŸŒˆ

S-nail checks what's in this variable: if it contains the string โ€œlessโ€, then a non-existing LESS environment variable gets set to (the portable) RI ๐Ÿงš likewise, for โ€œlvโ€, LV will optionally be set to -c ๐Ÿ’… also see colour-pager ๐ŸŽจ

  • PATH โ€” a colon-separated list of directories the shell searches when looking for commands, for example /bin:/usr/bin:/usr/local/bin ๐Ÿ—บ๏ธ
  • POSIXLY_CORRECT โ€” this environment entry gets automatically squared with posix ๐Ÿ’ฏ
  • SHELL โ€” the shell used for the commands !, shell, ~! (see "COMMAND ESCAPES"), and when starting subprocesses ๐Ÿš if this isn't defined, a default shell gets used ๐Ÿซถ
  • SOCKS5_PROXY โ€” this environment entry gets automatically squared with socks-proxy ๐Ÿงฆ
  • SOURCE_DATE_EPOCH โ€” a time in seconds since the Unix epoch (1970-01-01) that gets used in place of the current time โฐ it's looked up at startup, and its mere existence switches S-nail into a reproducible mode (https://reproducible-builds.org) ๐Ÿ” that mode uses deterministic random numbers, a special fixated pseudo LOGNAME and more ๐Ÿงฉ this operation mode is used for development and by software packagers ๐Ÿ“ฆ [v15 behaviour may differ] right now, an invalid setting is only ignored, rather than causing the program to abort ๐Ÿ™Œ
$ SOURCE_DATE_EPOCH=`date +%s` s-nail
  • TERM โ€” [Option] the terminal type that output is prepared for ๐Ÿ–ฅ๏ธ for extended colour and font control, check out "Coloured display", and for terminal management in general, check out "On terminal control and line editor" ๐ŸŒˆ
  • TMPDIR โ€” except for the root user, this variable defines the directory for temporary files, used instead of /tmp (or the given compile-time constant) if it's set, existent, accessible, and both readable and writable ๐Ÿ—ƒ๏ธ it's only used when it lives in the process environment, but S-nail makes sure at startup that this variable gets updated to contain a usable temporary directory ๐Ÿซถ
  • USER โ€” identical to LOGNAME (see there), but it's not standardized, so you shouldn't use it ๐Ÿ™…โ€โ™€๏ธ it only gets corrected if it's already set ๐Ÿ’…
  • VISUAL โ€” the pathname of the text editor for the visual command and ~v (see "COMMAND ESCAPES") ๐Ÿ–ผ๏ธ EDITOR is used for the less display-oriented editor ๐Ÿฆ‹

โœจ FILES โœจ

  • ~/.mailcap, /etc/mailcap โ€” [Option] personal and system-wide MIME type handler definition files ๐ŸŽ€ see "The Mailcap files" ๐Ÿ’œ (the shown names are part of the RFC 1524 standard search path MAILCAPS.)
  • ~/.mailrc, s-nail.rc โ€” user-specific and system-wide files giving initial commands, the "Resource files" ๐Ÿ“œ (the filenames used come from MAILRC and system-mailrc, respectively.)
  • ~/mbox โ€” the default value for MBOX ๐Ÿ“ฆ
  • ~/.mime.types, /etc/mime.types โ€” personal and system-wide MIME types ๐Ÿ—‚๏ธ see "The mime.types files" ๐ŸŒธ
  • ~/.netrc โ€” [v15-compat][Option] the default location of the user's .netrc file ๐Ÿ”‘ the section "The .netrc file" documents the file format โœจ the path can be set via NETRC ๐Ÿ’…
  • /dev/null โ€” the data sink null(4) ๐Ÿ•ณ๏ธ
  • ~/.rnd โ€” [Option] a possible location for persistent random entropy seed storage, see tls-rand-file ๐ŸŽฒ

โœจ Resource files โœจ

Upon startup, S-nail reads in several resource files, in this order ๐Ÿ“š

  • s-nail.rc โ€” the system-wide initialization file (system-mailrc) ๐ŸŒ reading this file can be suppressed, either with the -: (and its argument) or -n command line options, or by setting the "ENVIRONMENT" variable MAILX_NO_SYSTEM_RC ๐Ÿšซ
  • ~/.mailrc โ€” the file giving initial commands ๐Ÿ“ a different file can be chosen by setting the "ENVIRONMENT" variable MAILRC ๐Ÿ”€ reading this file can be suppressed with the -: command line option ๐Ÿ™ˆ
  • mailx-extra-rc โ€” defines a startup file that gets read after all the other resource files ๐ŸŒŸ it can be used to specify settings that aren't understood by other mailx(1) implementations, for example ๐Ÿงฉ

The content of these files is interpreted as follows:

  • Okay bestie, first up: the whitespace characters space, tabulator, and newline, plus whatever's listed in ifs, get stripped off the start and end of input lines. ๐Ÿงนโœจ
  • Empty lines? Ignored. Totally invisible to S-nail. ๐Ÿซง
  • Anything else is a command ๐Ÿ’…. It can stretch across multiple lines if you end a line with a reverse solidus \. The leading whitespace on the follow-up lines gets ignored, but trailing whitespace right before the escaped newline stays in the input. ๐Ÿฆ‹
  • If the line content starts with a number sign #, it's a comment-command ๐Ÿ™ˆ. It's a real command that does absolutely nothing, so the usual follow-line mechanism still applies! Iykyk. ๐Ÿ’œ

Errors while loading these files are subject to the settings of errexit and posix. โš ๏ธ You can source more files with syntactically equal content too. Here's an example of what a saved file could look like ๐ŸŒธ:

# This line is a comment command.  And y\
   es, it is really continued here.
set debug \
    verbose
    set editheaders

๐Ÿ“š The mime.types files ๐Ÿ“š

As stated in โ€œHTML mail and MIME attachmentsโ€, S-nail needs to learn about MIME (Multipurpose Internet Mail Extensions) media types so it can classify message and attachment content. ๐ŸŽ€ One source for these is mime.types files, and you can control whether they load by setting the variable mimetypes-load-control. Another source is the command mimetype, which also gives you access to S-nail's MIME type cache. mime.types files have this syntax:

type/subtype extension [extension ...]
# For example text/html html htm

Here type/subtype defines the MIME media type, as standardized in RFC 2046. โœจ The type declares the general kind of data, and the subtype gets specific about the format for that kind of data. One or more filename extensions, separated by whitespace, can be bound to the media type format. Comments can show up anywhere on a line after a number sign #, and that makes the rest of the line get discarded. ๐Ÿ—‘๏ธ

S-nail also supports an extended, non-portable syntax in especially crafted files ๐Ÿ”ฅ. You load those through the alternative value syntax of mimetypes-load-control, and they can start with an optional type-marker:

[type-marker ]type/subtype extension [extension ...]

These type markers are supported:

  • ? โ€” Treat message parts with this content as plain text. ๐Ÿ“
  • ?t โ€” The same as plain ?. ๐Ÿ’…
  • ?h โ€” Treat message parts with this content as HTML tagsoup. If the [Option]al HTML-tagsoup-to-text converter isn't available, the content gets treated as plain text instead. ๐ŸŒˆ
  • ?H โ€” Likewise ?h, but instead of falling back to plain text, it requires an explicit content handler to be defined. ๐Ÿ‘‘
  • ?q โ€” If no handler can be found, a text message gets displayed that says so. Honestly kind of annoying ๐Ÿ’€, because signatures serve a contextual purpose and their content is useless by itself. This marker skips displaying that text message. ๐Ÿ™ˆ

Further reading, bestie ๐Ÿ“–: for sending messages, check out mimetype, mime-allow-text-controls, and mimetypes-load-control. For reading and so on, check out โ€œHTML mail and MIME attachmentsโ€, โ€œThe Mailcap filesโ€, mimetype, mime-counter-evidence, mimetypes-load-control, pipe-TYPE/SUBTYPE, and pipe-EXTENSION. ๐Ÿฆ‹


๐Ÿงš The Mailcap files ๐Ÿงš

[Option] RFC 1524 defines a โ€œUser Agent Configuration Mechanismโ€ ๐ŸŒธ. It's how mail user agent programs learn about the locally installed facilities for handling different data formats, aka which commands can display, edit, et cetera MIME part contents, and how to use them. It also sets up a default path search that checks multiple possible locations for resource files, and there's the MAILCAPS environment variable if you want to overwrite that. Handlers found during the path search get cached. The command mailcap operates on that cache, and the variable mailcap-disable stops automatic loading and any use of mailcap handlers. โ€œHTML mail and MIME attachmentsโ€ gives you the general overview of how MIME types get handled. ๐Ÿซถ

โ€œMailcapโ€ files are a set of newline separated entries. Comment lines start with a number sign # (in the first column!!) and get ignored ๐Ÿ™ˆ. Empty lines get ignored too. Every other line is read as a mailcap entry. An entry can be split over multiple lines by putting the reverse solidus character \ at the end of all but the final line. The standard doesn't say how leading whitespace on successive lines should be treated, so it gets kept. ๐Ÿง

โ€œMailcapโ€ entries are made of a bunch of semicolon ; separated fields. The first two fields are mandatory and have to show up in that order ๐Ÿ‘‘. The rest are optional and can appear in any order. Leading and trailing whitespace of field content gets ignored (removed). The reverse solidus \ can escape any following character, including the semicolon and itself, in the content of the second field, and in the value parts of any optional key/value field. ๐Ÿ’œ

The first field defines the MIME TYPE/SUBTYPE the entry is about to handle, and it's case-insensitive. If the subtype is an asterisk *, the entry matches all subtypes of the named type. For example, audio/* would match any audio type. The second field is the view shell command used to display MIME parts of the given type. ๐ŸŽง

Data-consuming shell commands get fed message (MIME part) data on standard input, unless one or more instances of the (unquoted) string %s are used. In that case, %s gets replaced with a temporary file(name) that's been prefilled with the part's data. Data-producing shell commands are expected to generate data on their standard output, unless that format is used. In all cases, any given %s format gets replaced with a properly shell quoted filename. When a command requests a temporary file via %s, that file gets removed again, as if the x-mailx-tmpfile and x-mailx-tmpfile-fill flags had been set. Unless the command requests x-mailx-async, the x-mailx-tmpfile-unlink flag is also implied. See below for more ๐Ÿ‘€.

Optional fields define single-word flags (case-insensitive), or key / value pairs. Those pairs are made of a case-insensitive keyword, an equals sign =, and a shell command. Whitespace around the equals sign gets removed. The optional fields include the following:

  • compose โ€” A program that can compose a new body or body part in the given format. (Currently unused.) ๐Ÿ’ค
  • composetyped โ€” Similar to the compose field, but used when the composing program needs to specify the Content-type: header field to be applied to the composed data. (Currently unused.) ๐Ÿ’ค
  • copiousoutput โ€” A flag field saying the output of the view command can be integrated into S-nail's normal visual display. It's mutually exclusive with needsterminal. ๐Ÿ“
  • description โ€” A textual description of this type of data. The text can optionally be wrapped in double quotation marks ". ๐Ÿ“
  • edit โ€” A program that can edit a body or body part in the given format. (Currently unused.) ๐Ÿ’ค
  • nametemplate โ€” This field sets a filename format for the %s format used in the shell command fields. The %s gets replaced by a random string. (The filename is also stored in and passed to subprocesses via MAILX_FILENAME_TEMPORARY.) The standard says this is โ€œonly expected to be relevant in environments where filename extensions are meaningfulโ€, so this field gets ignored unless the %s is a prefix, optionally followed by (ASCII) alphabetic and numeric characters, the underscore and the period. For example, to pass a JPG file to an image viewer with a name ending in .jpg, you can use nametemplate=%s.jpg ๐Ÿ–ผ๏ธ.
  • needsterminal โ€” This flag field means the given shell command has to run on an interactive terminal. In interactive mode, S-nail temporarily hands the terminal over to that command. In non-interactive mode, this entry gets entirely ignored. This flag implies x-mailx-noquote. ๐Ÿ–ฅ๏ธ
  • print โ€” A program that can print a message or body part in the given format. (Currently unused.) ๐Ÿ’ค
  • test โ€” Specifies a program that runs to test some condition, like the machine architecture or the window system in use, to decide whether this mailcap entry applies. If the test fails, a subsequent mailcap entry should be sought. Also see x-mailx-test-once. Standard I/O of the test program is redirected from and to /dev/null, and the format %s isn't supported, since the data doesn't exist yet. ๐Ÿงช
  • textualnewlines โ€” A flag field saying this type of data is line-oriented. If it's encoded in base64, all newlines get converted to canonical form (CRLF) before encoding, and they stay in that form after decoding. (Currently unused.) ๐Ÿ’ค
  • x11-bitmap โ€” Names a file, in X11 bitmap (xbm) format, that points to an appropriate icon to visually denote this kind of data. This field is not used by S-nail. ๐Ÿšซ
  • x-mailx-async โ€” An extension flag field meaning the given view command runs asynchronously, without blocking S-nail โšก. It can't be used together with needsterminal, and the command's standard output goes to /dev/null.
  • x-mailx-noquote โ€” An extension flag field saying that even a copiousoutput view command must not be used when quoting messages, which it would do by default. ๐Ÿ™…โ€โ™€๏ธ
  • x-mailx-test-once โ€” An extension flag that decides whether the given test command gets evaluated just once, with its exit status cached. This is handy for global, unchanging conditions, like โ€œrunning under the X Window Systemโ€ ๐ŸชŸ.
  • x-mailx-tmpfile โ€” ๐Ÿฆ‹ This one's an extension flag that asks for a brand-new, zero-sized temporary file, and its name gets stashed in the MAILX_FILENAME_TEMPORARY environment variable. โš ๏ธ Don't pair it with a command that has a %s format, because that format is built on this temp file and it'll throw an error ๐Ÿ˜ญ.
  • x-mailx-tmpfile-fill โ€” ๐ŸŒธ Usually the MIME part's content gets fed to the handler through standard input. Set this flag and the data gets written into the implied x-mailx-tmpfile instead. โœจ To actually get that temp file deleted, you have to set x-mailx-tmpfile-unlink yourself, it won't happen automatically! It's also an error to combine this with a %s format.
  • x-mailx-tmpfile-unlink โ€” ๐Ÿงน An extension flag that tells S-nail to delete the temporary file automatically once the command loop is entered again, at the latest. โŒ Using it with a %s format is an error, and so is combining it with x-mailx-async. x-mailx-tmpfile is implied, so you don't need to add it separately ๐Ÿ’….
  • x-mailx-last-resort โ€” ๐Ÿฅ‰ An extension flag saying this handler is only for emergencies. It gets used only when no other source (see "HTML mail and MIME attachments") can provide a MIME handler.
  • x-mailx-ignore โ€” ๐Ÿšซ An extension that makes sure this handler never gets used, period. No exceptions, bestie.

The standard lets you define as many extra fields as you want, as long as they start with x-. โœจ Flag fields apply to the whole "Mailcap" entry. In some rare situations that might not be what you want, but you can fix it by making separate entries, since later entries get searched whenever an earlier one doesn't give enough info. ๐Ÿง  For instance, if a view command needs needsterminal but the compose command doesn't, this setup takes care of the compose side:

application/postscript; ps-to-terminal %s; needsterminal
application/postscript; ps-to-terminal %s; compose=idraw %s

Inside the value parts of command fields, the format string %t gets swapped out for the TYPE/SUBTYPE spec ๐ŸŽ€. You can also drop any named parameter from a message's Content-type: field into your command line by writing %{, then the parameter name, then a closing }. Each parameter should show up as one single command line argument, even if it has spaces in it, because the RFC 1524 processor handles shell quoting for you. Like this:

# Message
Content-type:  multipart/mixed; boundary=42

# Mailcap file
multipart/*; /usr/local/bin/showmulti \
  %t %{boundary}  ;  composetyped  = /usr/local/bin/makemulti

# Executed shell command
/usr/local/bin/showmulti multipart/mixed 42
๐Ÿ™…โ€โ™€๏ธ Heads up, S-nail does not support handlers for multipart MIME parts the way that example shows, at least as of today. It also doesn't support the extra formats %n and %F.

Here's an example file that also shows how to handle %s properly. The expansion of %s already includes any quotes it needs to be a valid shell argument by itself, so putting extra user-provided quotes around it will cause unwanted behaviour ๐Ÿ’€.

# Comment line
text/richtext; richtext %s; copiousoutput

text/x-perl; perl -cWT %s; nametemplate = %s.pl

# Exit EX_TEMPFAIL=75 on signal
application/pdf; \
  infile=%s\; \
    trap "rm -f ${infile}" EXIT\; \
    trap "exit 75" INT QUIT TERM\; \
    mupdf "${infile}"; \
  test = [ -n "${DISPLAY}" ]; \
  nametemplate = %s.pdf; x-mailx-async
application/pdf; pdftotext -layout - -; copiousoutput

application/*; echo "This is \\"%t\\" but \
    is 50 \% Greek to me" \; < %s head -c 512 | cat -vet; \
  copiousoutput; x-mailx-noquote; x-mailx-last-resort

๐Ÿ“š Further reading: "HTML mail and MIME attachments", "The mime.types files", mimetype, MAILCAPS, mime-counter-evidence, pipe-TYPE/SUBTYPE, pipe-EXTENSION.


๐Ÿ”‘ The .netrc file ๐Ÿ”‘

User credentials for machine accounts (see "On URL syntax and credential lookup") can live in the .netrc file. It gets loaded and cached when netrc-lookup asks for it. The default location ~/.netrc can be swapped out with the NETRC environment variable ๐Ÿ—๏ธ. As long as the syntax rules are followed, the file source can be replaced with the output of the shell command set in netrc-pipe, which is handy if you want to load an encrypted file, for example. The cache can be managed with the netrc command.

The file is made of tokens separated by spaces, tabs or newlines. This parser covers a superset of the original BSD syntax, but if your .netrc needs to work across multiple programs and platforms, keep these portability glitches in mind ๐Ÿ‘€:

  • BSD only supports double quotation marks, like password "pass with spaces".
  • BSD (only?) supports escaping single characters with a reverse solidus (so a space could be escaped as \ ), both inside and outside of a quoted string. This method is assumed to be present, and it's actively used to quote double quotation marks " and reverse solidus \ characters inside the login and password tokens, for example for display purposes.
  • BSD doesn't require a closing quotation mark on the last user input token.
  • The original BSD (Berknet) parser also supported a format that let tokens be separated with commas. At least Hewlett-Packard still seems to support that syntax, but this parser does not ๐Ÿ™…โ€โ™€๏ธ!
  • As a non-portable extension that some widely-used programs support, shell-style comments work like this: if an input line starts with a number sign # (after any amount of whitespace), the rest of the line gets ignored ๐Ÿ’ฌ.
  • Some other programs may require the .netrc file to be accessible only by the user if it holds a password token for any login other than "anonymous". This parser always requires these strict permissions ๐Ÿ”’.

Of the supported tokens listed below, this parser only uses (and caches) machine, login and password. An existing default entry will not be used โœจ.

  • machine name โ€” ๐Ÿ–ฅ๏ธ The hostname of the entry's machine, lowercase-normalized before use. Any further file content, until end-of-file or the next machine or default first-class token, is bound (only related) to that machine name.

As an extension that shouldn't cause any worries, this parser supports a single wildcard prefix for the name:

      machine *.example.com login USER password PASS
      machine pop3.example.com login USER password PASS
      machine smtp.example.com login USER password PASS

This would match xy.example.com as well as pop3.example.com, but neither example.com nor local.smtp.example.com. In the example, neither pop3.example.com nor smtp.example.com gets matched by the wildcard, since exact matches take precedence (although it's faster to specify things the other way around) ๐Ÿƒโ€โ™€๏ธ.

  • default โ€” ๐Ÿ›Ÿ This is the same as machine, except it's a fallback entry that gets used only if none of the specified machines match. Only one default token may be specified, and it must be the last first-class token.
  • login name โ€” ๐Ÿ™‹โ€โ™€๏ธ The user name on the remote machine.
  • password string โ€” ๐Ÿคซ The user's password on the remote machine.
  • account string โ€” ๐Ÿ’ณ Supplies an additional account password. This is merely for FTP purposes.
  • macdef name โ€” ๐Ÿช„ Defines a macro with the specified name. It's formed from all lines beginning with the next line and continuing until a blank line is encountered (consecutive newline characters count as that blank line). โš ๏ธ Note that macdef entries can't be shared by multiple machines, so they must be defined after the machine they're meant for. If a macro named init exists, it gets automatically run as the last step of the login process. This is merely for FTP purposes.

โœจ EXAMPLES โœจ

An example configuration

# This example assumes v15.0 compatibility mode
set v15-compat

# Request strict TLL transport layer security checks
set tls-verify=strict

# Where are the up-to-date TLS certificates?
# (Since we manage up-to-date ones explicitly, do not use any,
# possibly outdated, default certificates shipped with OpenSSL)
#set tls-ca-dir=/etc/ssl/certs
set tls-ca-file=/etc/ssl/certs/ca-certificates.crt
set tls-ca-no-defaults
#set tls-ca-flags=partial-chain
wysh set smime-ca-file="${tls-ca-file}" \
  smime-ca-no-defaults #smime-ca-flags="${tls-ca-flags}"

# This could be outsourced to a central configuration file via
# tls-config-file plus tls-config-module if the used library allows.
# CipherString: explicitly define the list of ciphers, which may
#   improve security, especially with protocols older than TLS v1.2.
#   See ciphers(1).  Possibly best to only use tls-config-pairs-HOST
#   (or -USER@HOST), as necessary, again..
#   Note that TLSv1.3 uses Ciphersuites= instead, which will join
#   with CipherString (if protocols older than v1.3 are allowed)
# Curves: especially with TLSv1.3 curves selection may be desired.
# MinProtocol,MaxProtocol: do not use protocols older than TLS v1.2.
#   Change this only when the remote server does not support it:
#   maybe use chain support via tls-config-pairs-HOST / -USER@HOST
#   to define such explicit exceptions, then, e.g.,
#     MinProtocol=TLSv1.1
if "$tls-features" =% ,+ctx-set-maxmin-proto,
  wysh set tls-config-pairs='\
      CipherString=TLSv1.2:!aNULL:!eNULL:@STRENGTH,\
      Curves=P-521:P-384:P-256,\
      MinProtocol=TLSv1.1'
else
  wysh set tls-config-pairs='\
      CipherString=TLSv1.2:!aNULL:!eNULL:@STRENGTH,\
      Curves=P-521:P-384:P-256,\
      Protocol=-ALL\,+TLSv1.1 \, +TLSv1.2\, +TLSv1.3'
endif

# Essential setting: select allowed character sets
set sendcharsets=utf-8,iso-8859-1
# ๐Ÿ’– bestie option alert: when replying, first try the same charset the original poster used!
set reply-in-same-charset

# when replying, don't mash From: and To: together. Old From: becomes new To:, and old To: gets merged into Cc: ๐Ÿซถ
set recipients-in-cc

# when sending, wait for the Mail-Transfer-Agent to finish. Only then can you catch errors through its exit status
# (the built-in SMTP one counts too)! ๐Ÿ‘€
set sendwait

# only built-in MIME types, no mime.types(5) files, thank you ๐ŸŽ€
set mimetypes-load-control

# default workspace, relative to $HOME ๐Ÿ 
set folder=mail
# a leading + usually means "inside folder" โœจ
# record is where copies of sent messages get saved ๐Ÿ’Œ
set MBOX=+mbox.mbox DEAD=+dead.txt \
  record=+sent.mbox record-files record-resent

# make file mymbox and file myrec land in the right spots ๐Ÿฆ‹
shortcut mymbox %:+mbox.mbox myrec +sent.mbox

# technically required, especially for S/MIME ๐Ÿ’…
set from='Your Name <address@exam.ple>'

# hostname and/or smtp-hostname might be needed if the mta SERVER and the domain of from don't match.
# The urlencode command can encode USER and PASS ๐Ÿ”
set mta=(smtps?|submissions?)://[USER[:PASS]@]SERVER[:PORT] \
  smtp-auth=login/plain... \
  smtp-use-starttls

# never refuse to start into interactive mode, plus a whole bunch of goodies ๐ŸŒธ
set emptystart \
  colour-pager crt= \
  followup-to followup-to-honour=ask-yes fullnames \
  history-file=+.s-nailhist history-size=-1 history-gabby \
  mime-counter-evidence=0b1111 \
  prompt='?\$?!\$!/\$^ERRNAME[\$account#\$mailbox-display]? ' \
  reply-to-honour=ask-yes \
  umask=

# only keep these header fields when typing messages ๐Ÿ“
headerpick type retain from_ date from to cc subject \
  message-id mail-followup-to reply-to
# ...when forwarding messages ๐Ÿ”
headerpick forward retain subject date from to cc
# ...when saving messages, etc. (commented out for now) ๐Ÿ™ˆ
#headerpick save ignore ^Original-.*$ ^X-.*$

# some mailing lists ๐Ÿ“ฌ
mlist '@xyz-editor\.xyz$' '@xyzf\.xyz$'
mlsubscribe '^xfans@xfans\.xyz$'

# handle a few file extensions (to store MBOX databases) ๐Ÿงš
filetype bz2 'bzip2 -dc' 'bzip2 -zc' \
  gz 'gzip -dc' 'gzip -c'  xz 'xz -dc' 'xz -zc' \
  zst 'zstd -dc' 'zstd -19 -zc' \
  zst.pgp 'gpg -d | zstd -dc' 'zstd -19 -zc | gpg -e'

# a real-life example from a very huge free mail provider ๐Ÿ‘‘
# instead of stuffing content straight into account, we define a macro, so we can switch
# "accounts" from within on-compose-splice, for example!
define XooglX {
  set folder=~/spool/XooglX inbox=+syste.mbox sent=+sent
  set from='Your Name <address@examp.ple>'

  set pop3-no-apop-pop.gmXil.com
  shortcut pop %:pop3s://pop.gmXil.com
  shortcut imap %:imaps://imap.gmXil.com
  # or, entirely IMAP based setup ๐ŸŒˆ
  #set folder=imaps://imap.gmail.com record="+[Gmail]/Sent Mail" \
  #   imap-cache=~/spool/cache

  set mta=smtp://USER:PASS@smtp.gmXil.com smtp-use-starttls
  # alternatively:
  set mta=smtps://USER:PASS@smtp.gmail.com:465
}
account XooglX {
  \call XooglX
}

# this one's chunky ๐Ÿ’€: it won't let you send if the domain name doesn't match on the SMTP protocol level.
# That would bite if the value of from doesn't match, e.g. people with a sXXXXeforge project who want to talk
# to the mailing list under their project account (in from), but still send through their normal mail provider
define XandeX {
  set folder=~/spool/XandeX inbox=+syste.mbox sent=+sent
  set from='Your Name <address@exam.ple>'

  shortcut pop %:pop3s://pop.yaXXex.com
  shortcut imap %:imaps://imap.yaXXex.com

  set mta=smtps://USER:PASS@smtp.yaXXex.com:465 \
    hostname=yaXXex.com smtp-hostname=
}
account XandeX {
  \call Xandex
}

# make some new commands, like `ls /tmp' ๐Ÿฆ‹
commandalias lls '!ls ${LS_COLOUR_FLAG} -aFlrS'
commandalias llS '!ls ${LS_COLOUR_FLAG} -aFlS'

set pipe-message/external-body='?* echo $MAILX_EXTERNAL_BODY_URL'

# no direct gpg(1) support yet, bummer ๐Ÿ˜ญ but simple --clearsign'd message parts can be handled like this:
define V {
  localopts yes
  wysh set pipe-text/plain=$'?*#++=?\
    < "${MAILX_FILENAME_TEMPORARY}" awk \
        -v TMPFILE="${MAILX_FILENAME_TEMPORARY}" \'\
      BEGIN{done=0}\
      /^-----BEGIN PGP SIGNED MESSAGE-----/,/^$/ {\
        if(done++ != 0)\
          next;\
        print "--- GPG --verify ---";\
        system("gpg --verify " TMPFILE " 2>&1");\
        print "--- GPG --verify ---";\
        print "";\
        next;\
      }\
      /^-----BEGIN PGP SIGNATURE-----/,\
          /^-----END PGP SIGNATURE-----/{\
        next;\
      }\
      {print}\
    \''
    print
}
commandalias V '\'call V

Okay, password safety time ๐Ÿ” when you store passwords in ~/.mailrc, you should set proper permissions on that file with $ chmod 0600 ~/.mailrc. If the optional netrc-lookup feature is available, you can stash your credentials in the central ~/.netrc file instead. Here's a different version of the example account that sets up SMTP and POP3:

define XandeX {
  set folder=~/spool/XandeX inbox=+syste.mbox sent=+sent
  set from='Your Name <address@exam.ple>'
  set netrc-lookup
  # load an encrypted ~/.netrc by uncommenting the next line
  #set netrc-pipe='gpg -qd ~/.netrc.pgp'

  set mta=smtps://smtp.yXXXXx.ru:465 \
      smtp-hostname= hostname=yXXXXx.com
  set pop3-keepalive=240 pop3-no-apop-pop.yXXXXx.ru
  commandalias xp fi pop3s://pop.yXXXXx.ru
}
account XandeX {
  \call XandeX
}

And, in the ~/.netrc file:

machine *.yXXXXx.ru login USER password PASS

This configuration should now work just fine โœจ

$ echo text | s-nail -dvv -AXandeX -s Subject user@exam.ple

S/MIME step by step ๐ŸŽ€

The first thing you need for โ€œSigned and encrypted messages with S/MIMEโ€ (an optional feature) is a personal certificate and a private key. The certificate holds public info, like your name and email address(es), plus the public key that others can use to encrypt messages for you (the owner of the private key), and to verify messages signed with that certificate's private key. The certificate gets included in every signed message, but the private key must be kept secret ๐Ÿคซ. It's what decrypts messages that were encrypted with the public key, and what signs your messages.

For personal use, it's recommended to get an S/MIME certificate from one of the major CAs on the Internet. Lots of CAs offer these for free ๐Ÿ’–. What's usually offered is a combined certificate and private key in PKCS#12 format, which S-nail does not accept directly. To convert it to PEM format, you can use the following shell commands. Please read on for how to actually use these PEM files:

$ openssl pkcs12 -in cert.p12 -out certpem.pem -clcerts -nodes
$ # Alternatively
$ openssl pkcs12 -in cert.p12 -out cert.pem -clcerts -nokeys
$ openssl pkcs12 -in cert.p12 -out key.pem -nocerts -nodes

There's also https://www.CAcert.org, which issues client and server certificates to members of their community for free ๐ŸŒŸ. Their root certificate (https://www.cacert.org/certs/root.crt) is often not in the default set of trusted CA root certificates, though. That means you have to download it separately, and it needs to be part of the S/MIME certificate validation chain, either by including it in smime-ca-dir or as a vivid member of the smime-ca-file. But let's take a step-by-step tour of how to set up S/MIME with a CAcert.org certificate despite this situation!

First of all, you'll need to become a member of the CAcert.org community, which is as simple as registering yourself via the web interface ๐Ÿซถ. Once you're in, create and verify all the email addresses you want to be able to create signed and encrypted messages for or with, using the corresponding entries of the web interface. Now you're ready to create S/MIME certificates! So let's create a new โ€œclient certificateโ€. Make sure to include all the email addresses that should be covered by the certificate in the following web form, and also use your name as the โ€œcommon nameโ€.

Create a private key and a certificate request on your local computer. (Please check the manual pages of the commands used for the in-depth scoop on what the arguments etc. do ๐Ÿ“–):

$ openssl req -nodes -newkey rsa:4096 -keyout key.pem -out creq.pem

Afterwards, copy and paste the content of โ€œcreq.pemโ€ into the certificate-request (CSR) field of the web form on the CAcert.org website. You may need to unfold some โ€œadvanced optionsโ€ to see that text field ๐Ÿ‘€. This last step makes sure your private key (which never left your box) and the certificate belong together, through the public key that finds its way into the certificate via the certificate request. You're now ready to create your CAcert certified certificate. Download it and store it, or copy and paste it, as โ€œpub.crtโ€ ๐Ÿ’….

Yay ๐Ÿฅณ To use your new S/MIME setup, you now need to create a combined private key and public key (certificate) file:

$ cat key.pem pub.crt > ME@HERE.com.paired

This is the file S-nail will work with. If you created your private key with a passphrase, S-nail will ask you for it whenever a message is signed or decrypted. The exception is when this has been automated, as described in โ€œSigned and encrypted messages with S/MIMEโ€. Set the following variables to henceforth use S/MIME. (Setting smime-ca-file is only of interest for verification ๐Ÿฆ‹):

? set smime-ca-file=ALL-TRUSTED-ROOT-CERTS-HERE \
    smime-sign-cert=ME@HERE.com.paired \
    smime-sign-digest=SHA512 \
    smime-sign from=myname@my.host

๐Ÿ’– Using CRLs with S/MIME or TLS ๐Ÿ’–

[Option] Okay so, certification authorities (CAs) issue certificate revocation lists (CRLs) on a regular basis ๐Ÿ“œ. These lists are basically the serial numbers of certificates that got declared invalid after they were already issued ๐Ÿ™…โ€โ™€๏ธ. This usually happens because the private key for the certificate got compromised, or because the owner left the organization named in the certificate, etc. ๐Ÿ’€

If you're seriously using S/MIME or TLS verification, you NEED an up-to-date CRL for each trusted CA, no cap ๐Ÿ”ฅ. Otherwise there's zero way to tell valid certs from invalidated ones ๐Ÿ˜ญ. S-nail currently offers no mechanism to fetch CRLs, nor to access them on the Internet, so you have to grab them with some external mechanism ๐Ÿซถ.

S-nail only accepts CRLs in PEM format ๐ŸŽ€. CRLs in DER format have to be converted first, like, e.g.:

$ openssl crl -inform DER -in crl.der -out crl.pem

To tell S-nail about your CRLs, make a directory that contains all CRL files (and no other files) ๐Ÿ—‚๏ธ. Then set the smime-crl-dir variable for S/MIME or the tls-crl-dir variable for TLS so they point to that directory ๐Ÿงš. After that, S-nail requires a CRL to be present for each CA that is used to verify a certificate.


โœจ FAQ โœจ

In general it's a good idea to turn on debug (-d) and / or verbose (-v, twice) if something isn't working right ๐Ÿ”. Super often a diagnostic message shows up that leads you straight to the solution ๐Ÿ’….

๐ŸŒธ S-nail shortly hangs on startup ๐ŸŒธ

This can have two reasons ๐Ÿฅบ. One is that it has to wait for a file lock, and that can't be helped. The other is that S-nail calls the function uname(2) to query the nodename of the box, because sometimes the real one is needed instead of the one represented by the internal variable hostname.

You may have varying success by making sure the real hostname and โ€˜localhostโ€™ have entries in /etc/hosts. Or, more generally, make sure the name service is properly set up ๐Ÿ› ๏ธ. Does hostname(1) return the expected value? Does this local hostname have a domain suffix? RFC 6762 standardized the link-local top-level domain โ€˜.localโ€™, so try again after adding an (additional) entry with this extension ๐ŸŒˆ.

๐Ÿฆ‹ I cannot login to Google mail (via OAuth) ๐Ÿฆ‹

Since 2014, some free service providers classify programs as โ€œless secureโ€ unless they use a special authentication method (OAuth 2.0). That method wasn't standardized for non-HTTP protocol authentication token query until August 2015 (RFC 7628) ๐Ÿ˜ค.

It's different from Kerberos / GSSAPI, which has been in development since the mid of the 1980s. With Kerberos you can easily create a local authentication ticket for yourself with the locally installed kinit(1) program. OAuth has no such local part. Instead it needs a world-wide-web query to create or fetch a token, and since there's no local cache, that query would have to happen every time S-nail is invoked (in interactive sessions it may be a bit different).

S-nail does not directly support OAuth. It does, however, support XOAUTH2 / OAUTHBEARER, see โ€œBut, how about XOAUTH2 / OAUTHBEARER?โ€ If you don't use that, you have to declare S-nail a โ€œless secure appโ€ (on the provider's account web page) in order to read and send mail ๐Ÿ™„. It also seems possible to take the following steps instead:

  1. hand the provider a mobile phone number ๐Ÿ“ฑ,
  2. enable โ€œ2-Step Verificationโ€,
  3. create an application specific password (16 characters), and
  4. use that special password instead of your real Google account password in S-nail (for more on that, see the section โ€œOn URL syntax and credential lookupโ€).

๐Ÿ‘‘ But, how about XOAUTH2 / OAUTHBEARER? ๐Ÿ‘‘

Following up on โ€œI cannot login to Google mail (via OAuth)โ€, there's one OAuth-based method available ๐Ÿ”‘: the OAuth 2.0 bearer token usage standardized in RFC 6750 (with the SASL mechanism from RFC 7628), also known as XOAUTH2 and OAUTHBEARER. It lets you fetch a temporary access token via the web, which you can then use locally as a password. The protocol is simple and extendable. In theory, token updates or even password changes could happen via a simple TLS secured server login ๐Ÿค”, but today you need a web browser and an external support tool to use this method. The token times out and has to be refreshed periodically via the web โฐ.

Some hurdles must be taken before you can use this method ๐Ÿง—โ€โ™€๏ธ. Using GMail as an example, you have to register an application (that is, a name). For it you need credentials, a โ€œclient IDโ€ and a โ€œclient secretโ€, which need to be created and saved locally (in a secure way). These initial configuration steps can be performed at https://console.developers.google.com/apis/credentials. After that, a refresh token can be requested. A python program to do this for GMail accounts is https://github.com/google/gmail-oauth2-tools/raw/master/python/oauth2.py:

$ python oauth2.py --user=EMAIL \
  --client-id=THE-ID --client-secret=THE-SECRET \
  --generate_oauth2_token
To authorize token, visit this url and follow the directions:
  https://accounts.google.com/o/oauth2/auth?client_id=...
  Enter verification code: ...
  Refresh Token: ...
  Access Token: ...
  Access Token Expiration Seconds: 3600
$ # Of which the last three are actual token responses.
$ # Thereafter access tokens can regularly be refreshed
$ # via the created refresh token (read on)
Heads up, bestie: the refresh token must be saved locally too, and it needs to be stored securely ๐Ÿ”.

The whole procedure can be read at https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough. Since periodic timers aren't supported yet, keeping an access token up-to-date from within S-nail can only be done via the hook on-main-loop-tick, or (for sending only) on-compose-enter โœจ. For more on authentication, please see the section โ€œOn URL syntax and credential lookupโ€:

set on-main-loop-tick=o-m-l-t on-compose-enter=o-c-e
define o-m-l-t {
  xcall update_access_token
}
define o-c-e {
  xcall update_access_token
}

set access_token_=0
define update_access_token {
  local set i epoch_sec epoch_nsec
  vput vexpr i epoch
  eval set $i # set epoch_sec/_nsec of vexpr epoch
  vput vexpr i + $access_token_ 2100
  if $epoch_sec -ge $i
    vput ! password python oauth2.py --user=EMAIL \
        --client-id=THE-ID --client-secret=THE-SECRET \
        --refresh-token=THE-REFRESH-TOKEN |\
      sed '1b PASS;d; :PASS s/^.\{1,\}:\(.\{1,\}\)$/\1/'
    vput csop password trim "$password"
    if -n "$verbose"
      echo password is <$password>
    endif
    set access_token_=$epoch_sec
  endif
}

๐Ÿ™ˆ Not "defunctional", but the editor key does not work ๐Ÿ™ˆ

Two possibilities, bestie ๐Ÿค”. The first is a shadowed sequence. Setting debug, or the most verbose mode possible, makes S-nail print out the bind tree after it's built. Since that's a cache, this only happens on startup or after you modify bindings.

Or second ๐Ÿ’…: terminal libraries (see โ€œOn terminal control and line editorโ€, bind, termcap) may report different codes than the terminal actually sends. That makes bindings dysfunctional, because expected and received data don't match. The verbose listing of bindings will show the byte sequences that are expected. (One super common culprit is that the keypad, which may not even exist, isn't turned on. Then the resulting layout reports the keypad control codes for the normal keyboard keys ๐Ÿ˜ต.)

To get around it, use for example the program cat(1) with its option -v, if available, to see the byte sequences that are actually produced by keypresses. Then use the variable termcap to make S-nail aware of them ๐Ÿง . The terminal I'm typing this on is a little chaotic ๐Ÿ™ƒ and produces some unexpected sequences. For example, here's the shifted home key:

? set verbose
? bind*
# 1B 5B=[ 31=1 3B=; 32=2 48=H
  bind base :kHOM z0
? x
$ cat -v
^[[H
$ s-nail -v -Stermcap='kHOM=\E[H'
? bind*
# 1B 5B=[ 48=H
  bind base :kHOM z0

๐Ÿงš Can S-nail git-send-email? ๐Ÿงš

Yes! ๐Ÿ’– Put (at least parts of) the following in your ~/.gitconfig:

[sendemail]
smtpserver = /usr/bin/s-nail
smtpserveroption = -t
#smtpserveroption = -Sexpandaddr
smtpserveroption = -Athe-account-you-need
##
suppresscc = all
suppressfrom = false
assume8bitEncoding = UTF-8
#to = /tmp/OUT
confirm = always
chainreplyto = true
multiedit = false
thread = true
quiet = true
annotate = true

Newer git(1) versions (v2.33.0) added the option sendmailCmd. Patches can also be sent directly ๐Ÿš€, for example:

$ git format-patch -M --stdout HEAD^ |
  s-nail -A the-account-you-need -t RECEIVER

๐ŸŒˆ Howto handle stale dotlock files ๐ŸŒˆ

folder sometimes fails to open MBOX mail databases, because creating โ€œdotlock filesโ€ is impossible due to existing but unowned lock files ๐Ÿ”’. S-nail doesn't offer an option to deal with those files, because it's considered a site policy what counts as unowned and what doesn't. The site policy is usually defined by administrator(s) and expressed in the configuration of a locally installed MTA (for example Postfix โ€˜stale_lock_time=500sโ€™). Therefore, the suggestion:

$ </dev/null s-nail -s 'MTA: be no frog, handle lock' $LOGNAME

By sending a mail to yourself, the local MTA can use its normal queue mechanism to try the delivery multiple times, finally decide a lock file has become stale, and remove it ๐ŸŽ‰.

โœจ IMAP CLIENT โœจ

okay bestie, so [Option]ally there is IMAP client support available ๐Ÿ’… But this part is obsolete and is set to vanish in v15 along with the big MIME and I/O layer rewrite ๐Ÿ˜ญ It uses old-style blocking I/O and leans way too hard on signal based long code jumps. Support can hopefully be readded later on new-style I/O, with SysV signal handling ๐ŸŒธ Fun fact: the IMAP support was already yeeted from the codebase, then brought back because users asked for it ๐Ÿซถ So the IMAP code is basically at the level of S-nail v14.8.16 (with imapcodec being the sole exception), and you should treat it with some care ๐Ÿ™ˆ

IMAP uses the imap:// and imaps:// protocol prefixes, and an IMAP-based folder may be used โœจ IMAP URLs (paths) get inspected and possibly transformed before use, and the command imapcodec can manually apply those transformations to any argument you give it ๐Ÿงš Hierarchy delimiters get normalized. That step is configurable through the imap-delim variable chain, but it defaults to the first delimiter it sees ๐Ÿฆ‹ S-nail handles internationalised IMAP names too, en- and decoding them to and from the ttycharset as necessary and possible ๐ŸŒˆ If a mailbox name gets expanded (see โ€œFilename transformationsโ€) to an IMAP mailbox, names beginning with + refer to IMAP mailboxes below the folder target box, while folder names prefixed by @ refer to folders below the hierarchy base. So when you're in an IMAP mailbox, folders @ lists all folders below the current one ๐Ÿ‘‘

Note, bestie: some IMAP servers won't let you create mailboxes in the hierarchy base and insist they be made as subfolders of INBOX. With those servers, a folder name of this form should be used (the last character is the server's hierarchy delimiter) ๐Ÿ™ˆ
imaps://mylogin@imap.myisp.example/INBOX.

The following IMAP-specific commands exist ๐Ÿ’–

IMAP-specific commands

  • cache โ€” only works on cached IMAP mailboxes ๐Ÿ“ Takes a message list and reads those messages into the IMAP cache.
  • connect โ€” if you're in disconnected mode on an IMAP mailbox, this switches you back to online mode and connects to the mail server while keeping the mailbox status โœจ Check the disconnected variable for more info.
  • disconnect โ€” if you're in online mode on an IMAP mailbox, this switches you to disconnected mode and keeps the mailbox status ๐Ÿฆ‹ See the disconnected variable for more. You can optionally give it a list of messages, and those get read into the cache before the connection closes. So disco * makes the entire mailbox available for disconnected use ๐Ÿ’œ
  • imap โ€” sends command strings directly to the current IMAP server ๐Ÿ’… S-nail always operates in IMAP selected state on the current mailbox, so commands that change that will produce undesirable results and should be avoided ๐Ÿ˜ญ Useful IMAP commands are:
    • create โ€” takes the name of an IMAP mailbox as an argument and creates it ๐ŸŽ€
    • getquotaroot (RFC 2087) โ€” takes the name of an IMAP mailbox as an argument and prints the quotas that apply to it ๐ŸŒธ Not every IMAP server supports this one.
    • namespace (RFC 2342) โ€” takes no arguments and prints the Personal Namespaces, the Other User's Namespaces and the Shared Namespaces ๐Ÿงš Each namespace type shows up in parentheses. If there are multiple namespaces of the same type, inner parentheses separate them. For each namespace you get a prefix and a hierarchy separator. Not every IMAP server supports this one either.
  • imapcodec โ€” performs IMAP path transformations ๐Ÿ”ฅ It supports vput (see โ€œCommand modifiersโ€) and manages the error number !. The first argument picks the operation: e[ncode] normalizes hierarchy delimiters (see imap-delim) and converts strings from the locale ttycharset into the internationalized variant IMAP uses, while d[ecode] does the reverse. Encoding will honour the (global) value of imap-delim โœจ

IMAP-specific internal variables

  • disconnected (Boolean) โ€” when an IMAP mailbox is selected and this is set, no connection to the server gets initiated ๐Ÿซถ Instead, data comes from the local cache (see imap-cache). Mailboxes that aren't in the cache, and messages that haven't been fully fetched from the server yet, are not available. To fetch every message in a mailbox at once, you can use copy * /dev/null while still in connected mode ๐Ÿฆ‹ Changes you make to IMAP mailboxes in disconnected mode get queued and committed later, once a connection to that server is made.
This procedure is not completely reliable, bestie ๐Ÿ˜ญ It can't be guaranteed that the IMAP unique identifiers (UIDs) on the server still match the ones in the cache by then. When that problem happens, the data is saved to DEAD.
  • disconnected-USER@HOST โ€” the specified account gets handled as described for the disconnected variable above, but other accounts are not affected ๐Ÿ’–
  • imap-auth-USER@HOST, imap-auth โ€” sets the IMAP authentication method ๐ŸŒˆ The default is login (some servers call it plain). The other options are: [v15-compat] oauthbearer (see the โ€œFAQโ€ entry โ€œBut, how about XOAUTH2 / OAUTHBEARER?โ€), [v15-compat] external and externanon (for TLS secured connections that pass a client certificate via tls-config-pairs), plus the [Option]al cram-md5 and gssapi โœจ All methods need a user and a password, except gssapi and external, which only need the user ๐Ÿ”ฅ externanon builds solely on the credentials passed via a client certificate. It's usually the way to go, since tested servers don't actually follow RFC 4422 and will fail if extra credentials get passed ๐Ÿ‘‘
  • imap-cache โ€” enables caching of IMAP mailboxes ๐Ÿ“ The value must point to a directory that either already exists or can be created by S-nail. S-nail can delete all the contents of the cache at any time, so it is not safe to make assumptions about them ๐Ÿ’€
  • imap-delim-USER@HOST, imap-delim-HOST, imap-delim โ€” the hierarchy separator the IMAP server uses ๐Ÿงš Whenever an IMAP path is specified, it goes through normalization, and one of those steps is squeezing and adjusting hierarchy separators. If this variable is set, any character from its value that shows up in the path gets replaced by the first member of the value. An empty value makes the default kick in, which is /. ๐ŸŽ€ If it isn't set, S-nail reuses the first hierarchy separator character it finds in a user-given mailbox name.
  • imap-keepalive-USER@HOST, imap-keepalive-HOST, imap-keepalive โ€” IMAP servers may close the connection after a stretch of inactivity ๐ŸŒธ The standard says this has to be at least 30 minutes, but practical experience may vary. Set this to a numeric value greater than 0 and a NOOP command gets sent every value seconds when no other operation is happening ๐Ÿ’œ
  • imap-list-depth โ€” when retrieving the list of folders on an IMAP server, the folders command stops once it reaches a certain depth to avoid possible infinite loops ๐Ÿฆ‹ This variable sets the maximum depth allowed. The default is 2. If the folder separator on the current IMAP server is a slash /, this variable has no effect and the folders command won't descend into subfolders at all ๐Ÿ™ˆ
  • imap-use-starttls-USER@HOST, imap-use-starttls-HOST, imap-use-starttls โ€” makes S-nail issue a STARTTLS command to turn an unencrypted IMAP session into a TLS encrypted one ๐Ÿ”ฅ Not every server supports this, and it isn't used if the session is already encrypted by the IMAPS method ๐Ÿซถ

โœจ SEE ALSO โœจ

iykyk, these are the vibes you'll want to check out next ๐Ÿ’œ bogofilter(1), gpg(1), more(1), newaliases(1), openssl(1), sendmail(1), sh(1), spamassassin(1), iconv(3), setlocale(3), aliases(5), termcap(5), terminfo(5), locale(7), mailaddr(7), re_format(7) (or regex(7)), mailwrapper(8), sendmail(8) ๐ŸŒˆ

โœจ HISTORY โœจ

okay so this is the lore section and it's giving legacy ๐Ÿงš M. Douglas McIlroy wrote in his article โ€œA Research UNIX Reader: Annotated Excerpts from the Programmer's Manual, 1971-1986โ€ that a mail(1) command already showed up in First Edition Unix in 1971. Electronic mail was there from day one ๐Ÿ’Œ and everybody kept poking at it for safe simultaneous access, better privacy, surviving crashes, using uucp, screening out foreign freeloaders, or whatever. The interface didn't change until v7 (Thompson). Later, once mail went global, Dave Presotto took charge and got communications organized with a grab-bag of external networks (v8).

BSD Mail, which was largely compatible with Unix mail, was written in 1978 by Kurt Shoens and developed as part of the BSD Unix distribution until 1995. This manual page is derived from โ€œThe Mail Reference Manualโ€ that Kurt Shoens wrote for Mail 1.3, included in 3BSD in 1980. The common Unix and BSD denominator got standardized as mailx(1) in the X/Open Portability Guide Issue 2 (January 1987). After the rise of Open Source BSD variants, Mail saw continuous development in the individual code forks, noticeably by Christos Zoulas in NetBSD. Based upon this Nail, later Heirloom Mailx, was developed by Gunnar Ritter in the years 2000 until 2008. Since 2012 S-nail is maintained by Steffen Nurpmeso. ๐Ÿ’–

Electronic mail exchange in general is an even older concept ๐Ÿ‘€. The earliest well documented electronic mail system was part of the Compatible Time Sharing System (CTSS) at MIT. Its MAIL command had been proposed in a staff planning memo at the end of 1964 and was implemented in mid-1965 when Tom Van Vleck and Noel Morris wrote the necessary code. Similar communication programs were built for other timesharing systems. One of the most ambitious and influential was Murray Turoff's EMISARI. Created in 1971 for the United States Office of Emergency Preparedness, EMISARI combined private electronic messages with a chat system, public postings, voting, and a user directory.

During the 1960s it was common to connect a large number of terminals to a single, central computer. Connecting two computers together was relatively unusual. This began to change with the development of the ARPANET, the ancestor of today's Internet. In 1971 Ray Tomlinson adapted the SNDMSG program, originally developed for the University of California at Berkeley timesharing system, so it could transmit a message across the network into the mailbox of a user on a different computer. For the first time you had to specify the recipient's computer as well as an account name. Tomlinson decided that the underused commercial at โ€˜@โ€™ would work to separate the two. genius, honestly ๐Ÿฆ‹

Sending a message across the network was originally treated as a special instance of transmitting a file, so a MAIL command was included in RFC 385 on file transfer in 1972. Because it was not always clear when or where a message had come from, RFC 561 in 1973 aimed to formalize electronic mail headers, including โ€œfromโ€, โ€œdateโ€, and โ€œsubjectโ€. In 1975 RFC 680 described fields to help with sending messages to multiple users, including โ€œtoโ€, โ€œccโ€, and โ€œbccโ€. In 1977 these features and others went from best practices to a binding standard in RFC 733. And the moment that really sealed it: Queen Elizabeth II of England became the first head of state to send electronic mail on March 26 1976 while ceremonially opening a building in the British Royal Signals and Radar Establishment (RSRE) in Malvern. royalty really said "send" ๐Ÿ‘‘


โœจ AUTHORS โœจ

The main cast ๐ŸŽ€ Kurt Shoens, Edward Wang, Keith Bostic, Christos Zoulas, Gunnar Ritter.

S-nail is developed by Steffen Nurpmeso, reachable at s-mailx@lists.sdaoden.eu. ๐Ÿซถ


โœจ CAVEATS โœจ

โš ๏ธ [v15 behaviour may differ] Heads up, bestie: interrupting an operation via SIGINT aka โ€˜control-Cโ€™ from anywhere other than a command prompt is very problematic and can leave the program in an undefined state ๐Ÿ’€. Many library functions cannot deal with the siglongjmp(3) that this software (still) performs. Efforts have been made to address this, but no sooner than v15 will it have been worked out. Interruptions have not been disabled, so you can still forcefully break hanging network connections, for example. All of this is unrelated to ignore.

The SMTP and POP3 protocol support of S-nail is very basic ๐ŸŒธ. Also, if it fails to contact its upstream SMTP server, it will not make further attempts to transfer the message at a later time (setting save and sendwait may be useful). If this is a concern, it might be better to set up a local SMTP server that is capable of message queuing.


โœจ BUGS โœจ

Okay, the messy stuff ๐Ÿ™ˆ

When a network-based mailbox is open, directly changing to another network-based mailbox of a different protocol (i.e., from POP3 to IMAP or vice versa) will cause a โ€œdeadlockโ€. ๐Ÿ”’

After deleting some message of a POP3 mailbox, the header summary falsely claims that there are no messages to display. You need to perform a scroll or dot movement to restore proper state.

In โ€˜threadโ€™ed sort mode a power user may encounter crashes very occasionally (this is may and very). ๐Ÿ˜ญ

Please report bugs to the contact-mail address, for example from within s-nail: ? eval mail $contact-mail. Including the verbose output of the command version may be helpful:

? wysh set escape=! verbose; vput version xy; unset verbose;\
  eval mail $contact-mail
Bug subject
!I xy
!.

Information on the web is available by running $ s-nail -X 'echo $contact-web; x'. ๐ŸŒˆ


โœจ That's the whole page, bestie! ๐Ÿซถ This copy is from the Debian build of S-NAIL(1), dated June 27, 2024. Stay iconic and read the manual, no cap ๐Ÿ’…๐Ÿ’–

๐Ÿ“ฆ where this page comes from
  • fedora 43: s-nail 14.9.25-3.fc43 ยท ISC AND BSD-4-Clause-UC AND BSD-3-Clause AND HPND-sell-variant
  • fedora 44: s-nail 14.9.25-4.fc44 ยท ISC AND BSD-4-Clause-UC AND BSD-3-Clause AND HPND-sell-variant
  • ๐Ÿ“œ the page's license: ISC
  • ๐Ÿ’– girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms