README.listtexts

List texts in Mlmmj
===================

List texts are stored in listdir/text and subdirectories of
prefix/share/mlmmj/text.skel. They specify the content of various automatic
emails that Mlmmj sends. They are provided in a number of different languages.
The language to use for a list is chosen when you run the mlmmj-make-ml script
and the appropriate files are copied into your listdir/text directory.

This file documents the following aspects of list texts:

- Naming scheme
- Supported list texts
- Format
- Conditionals
- Wrapping
- Formatting and comments
- Formatted substitutions
- Unformatted substitutions
- Escapes

Naming scheme
-------------

List texts are named following a scheme of:

  purpose-action-reason-type

Mlmmj will look for the full four-part name first, then for files with shorter
names obtained by dropping parts off the end, and finally for a file with a
compatibility filename. It will use the first one it finds. (Note that use of
the compatibility filename is DEPRECATED and will be removed in a future
release.)

So, the complete search order is:

- purpose-action-reason-type
- purpose-action-reason
- purpose-action
- purpose
- compatibility filename (DEPRECATED)

When using shortened names, the %ifaction%, %ifreason%, %iftype% and related
conditionals can be used to customise the list text according to the values of
the missing parts.

Mlmmj checks these three paths for each candidate filename, and then moves on
to the next candidate filename:

- listdir/text
- prefix/share/mlmmj/text.skel/default
- prefix/share/mlmmj/text.skel/en

The second path does not exist by default, but can be created by copying or
symlinking the language of your choice to that path.

Note that this search order means that if there is a more specific list text in
a system directory, it will override a less-specific or compatibility list text
in the listdir. This may be surprising, and may change in a future version, so
should not be relied upon. Best practice is to ensure each list has its own
copy of all textx present in system directories, or none of them.

Supported list texts
--------------------

The following list texts are supported. The compatibility filename (DEPRECATED)
is given in brackets. Those with asterisks (*) are not yet used.

- help (listhelp)
  sent in response to an email to listname+help@domain.tld

- faq (listfaq)
  sent in response to an email to listname+faq@domain.tld

- confirm-sub-{request|admin}-normal (sub-confirm)
- confirm-sub-{request|admin}-digest (sub-confirm-digest)
- confirm-sub-{request|admin}-nomail (sub-confirm-nomail)
- confirm-unsub-{request|admin}-normal (unsub-confirm)
- confirm-unsub-{request|admin}-digest (unsub-confirm-digest)
- confirm-unsub-{request|admin}-nomail (unsub-confirm-nomail)
  sent to a requester to allow them to confirm a (un-)subscription request

- moderate-post-{modnonsubposts|access|moderated} (moderation)
  sent to the appropriate moderators when moderation is required because a user
  has submitted a post

- gatekeep-sub-{request|admin|confirm}-{normal|digest|nomail} (submod-moderator)
  sent to the appropriate gatekeepers when gatekeeping is required because a
  subscription request has been received

- wait-post-{modnonsubposts|access|moderated} (moderation-poster)
  sent to a person submitting a post when they need to wait for moderation
  before it is released to the list

- wait-sub-{request|admin|confirm}-{normal|digest|nomail} (submod-requester)
  sent to a person requesting subscription when they need to wait for
  gatekeeping for permission to join

- deny-sub-disabled-{digest|both} (sub-deny-digest)
- deny-sub-disabled-nomail (sub-deny-nomail)
- deny-sub-subbed-{normal|digest|nomail|both} (sub-subscribed)
- deny-sub-closed *
- deny-sub-expired *
- deny-sub-obstruct *
- deny-unsub-unsubbed-{normal|digest|nomail|all} (unsub-notsubscribed)
- deny-post-subonlypost (subonlypost)
- deny-post-modonlypost
- deny-post-access (access)
- deny-post-maxmailsize (maxmailsize)
- deny-post-tocc (notintocc)
- deny-post-expired *
- deny-post-reject *
- deny-release-notfound *
- deny-release-moderators *
- deny-reject-notfound *
- deny-reject-moderators *
- deny-permit-notfound *
- deny-permit-gatekeepers *
- deny-obstruct-notfound *
- deny-obstruct-gatekeepers *
  sent to the requestor when an action is denied or fails for some reason
  ('requestor' here means the person who requested the action, so e.g. for a
  reject action, deny-reject will go to the moderator requesting the rejection
  if the rejection fails; but deny-post-reject will go to the person requesting
  the post if the rejection succeeds, causing the post to fail)

- finish-sub-{request|confirm|admin|permit|switch}-normal (sub-ok)
- finish-sub-{request|confirm|admin|permit|switch}-digest (sub-ok-digest)
- finish-sub-{request|confirm|admin|permit|switch}-nomail (sub-ok-nomail)
- finish-unsub-{request|confirm|admin}-normal (unsub-ok)
- finish-unsub-{request|confirm|admin}-digest (unsub-ok-digest)
- finish-unsub-{request|confirm|admin}-nomail (unsub-ok-nomail)
- finish-post-request *
- finish-post-confirm *
- finish-post-release *
- finish-release *
- finish-reject *
- finish-permit *
- finish-obstruct *
  sent to the requestor when an action completes successfully
  ('requestor' here means the person who requested the action, so e.g. for a
  release action, the moderator requesting the release will receive
  finish-release, and the person who submitted the released post will receive
  finish-post-release because the release action caused their post action to
  succeed)

- notify-sub-{request|confirm|admin|permit}-normal (notifysub)
- notify-sub-{request|confirm|admin|permit}-digest (notifysub-digest)
- notify-sub-{request|confirm|admin|permit}-nomail (notifysub-nomail)
- notify-unsub-{request|confirm|admin|bouncing}-normal (notifyunsub)
- notify-unsub-{request|confirm|admin|bouncing}-digest (notifyunsub-digest)
- notify-unsub-{request|confirm|admin|bouncing}-nomail (notifyunsub-nomail)
  sent to the list owner when somebody is (un-)subscribed

- digest
  sent at the start of a digest (NOTE: the only header supported in this list
  text so far is a single-line 'Subject:' header; however, the contents of
  control/customheaders is included when digests are sent)

- probe (bounce-probe)
  sent to a subscriber after an email to them bounced to inform them of the
  bounce and probe when the address is no longer bouncing

- list---all (listsubs)
- list---normal *
- list---digest *
- list---nomail *
  sent in response to an email to listname+list@domain.tld from the list owner
  (DEPRECATED: if none of %listsubs%, %digestsubs% and %nomailsubs% is
  encountered in the text, then they will be automatically added with some
  default formatting; this functionality is expected to be removed in the
  future)

* Not yet used.

Format
------

List texts have the following format:

- Headers
- Blank line
- Body

They are expected to be in UTF-8 encoding and have Unix line endings.

The headers should be formatted as they should appear in the mail message. They
will begin the mail message. Header continuation via lines beginning with
linear whitespace is supported.

Following the headers found in the list text, Mlmmj will output the following
default headers, unless the same header is already provided in the list text.

- From:
- To:
- Message-ID:
- Date:
- Subject: mlmmj administrivia
- MIME-Version: 1.0
- Content-Type: text/plain; charset=utf-8
- Content-Transfer-Encoding: 8bit

The Subject: header is treated specially: it may include UTF-8 characters,
which will automatically be escaped using the =?utf-8?q?...?= quoting
mechanism.

(NOTE: the 'digest' list text is a bit different. See its description above.)

Both headers and bodies of list texts may include conditionals, formatting
directives and substitutions. These are explained in the following sections.
 
Conditionals
------------

Conditionals allow text in list texts to be included or omitted based on
conditions. The following are available:

- %ifaction A ...%
  the action is one of those given

- %ifreason R ...%
  the reason is one of those given

- %iftype T ...%
  the type is one of those given

- %ifcontrol C ...%
  one of the given control files exists

- %ifnaction A%
  the action is not the one given

- %ifnreason R%
  the reason is not the one given

- %ifntype T%
  the type is not the one given

- %ifncontrol C ...%
  at least one of the given control files does not exist

The text after the %if...% directive is only included if the condition is
satisfied, until an %else% or %endif% is encountered. These behave as you
would expect. The %else% is optional.

If a line with any of these conditional directives (%if...%, %else% or
%endif%), after processing, contains only whitespace, the line does not appear
at all in the output (the newline and any whitespace is omitted).

Furthermore, if the preceding processed output ends with a blank line, when an
unsatisfied conditional is encountered which has no %else% part, that
preceding blank line is removed (unless it is the blank line that ends the
headers).

On the whole, this is what you would want and expect, so you probably don't
need to worry about it.

Note that when multiple parameters can be given for the directives, these have
'or' behaviour; to get 'and' behaviour, nest conditionals.

Wrapping
--------

There are various directives available to assist with wrapping and formatting.
Wrapping needs to be enabled for each paragraph with:

- %wrap%
- %wrap W%
  concatenate and rewrap lines until the next empty line, whitespace-only line,
  or %nowrap% directive to a width of W (or 76 if W is omitted); second and
  later lines are preceded with as many spaces as the width preceding the
  directive; the width is reckoned including any text preceding the directive
  and any indentation preserved from a file which included the current one, so
  it is an absolute maximum width

To turn off wrapping before the end of a paragraph, use:

- %nowrap%
  stop wrapping; usually placed at the end of a line so the following line
  break is honoured but all preceding text is properly wrapped; if you want
  wrapping to continue after the break, you need to use %wrap% to turn it on
  again on the following line

To cater for various languages, there are a number of different wrapping modes
that can be set. These can be set either before or after wrapping is specified,
and can even be changed part way through a paragraph if desired. The following
directives control them:

- %wordwrap%
- %ww%
  use word-wrapping (this is the default; good for English, French, Greek and
  other languages that use an alphabet and spaces between words); lines have
  whitespace trimmed from both ends and are joined with a single space; lines
  are broken at spaces or at points marked for breaking with \/, but not at
  spaces escaped with a backslash

- %charwrap%
- %cw%
  use character-wrapping (good for Chinese, Japanese and Korean which use
  characters without spaces between words); lines have only leading whitespace
  trimmed and are joined without inserting anything at the joint; lines are
  broken at space or any non-ASCII character except where disallowed with \=

- %userwrap%
- %uw%
  use user-wrapping (for more complex languages or wherever complete manual
  control is desired); lines have only leading whitespace trimmed and are
  joined without inserting anything at the joint; lines are broken only where
  marked for breaking with \/

- %thin%
  assume non-ASCII characters are thin (equivalent to one unit, the same as
  ASCII characters) when reckoning the width for wrapping (this is the default;
  good for languages like Greek which use a non-Latin alphabet)

- %wide%
  assume non-ASCII characters are wide (equivalent to two units, twice as wide
  as ASCII characters) when reckoning the width for wrapping (good for Chinese,
  Japanese, Korean)

- %zero ABC%
  (ABC represents a sequence of non-ASCII characters)
  treat the listed characters as having zero-width when reckoning the width for
  wrapping (useful for ignoring combining characters such as accents so they
  don't affect the width calculation); usefully, the listed characters can be
  represented as unicode escapes (\uNNNN)

If a line with any of the directives in this section, after processing,
contains only whitespace, the line does not appear at all in the output (the
newline and any whitespace is omitted).

Formatting and comments
-----------------------

The following directives are available to assist with formatting and
readability:

- %^%
  start the line here; anything preceding this directive is ignored (useful for
  using indentation for readability without ruining the formatting of the text
  when it is processed)

- %comment%
- %$%
  end the line here; anything following this directive is ignored/a comment

If a line with any of these directives, after processing, contains only
whitespace, the line does not appear at all in the output (the newline and any
whitespace is omitted).

Formatted substitutions
-----------------------

These formatted substitutions work with multiple lines, so are generally not
appropriate for use in headers. They are:

- %text T%
  text from the file named T in the listdir/text directory; the name may only
  include letters, digits, underscore, dot and hyphen, and may not start with a
  dot; note that there is an unformatted version of this directive

- %control C%
  the contents of the control file named C in listir/control; the name may only
  include letters, digits, underscore, dot and hyphen, and may not start with a
  dot; note that there is an unformatted version of this directive

- %originalmail%
- %originalmail N%
  (available only in moderate-post-*, wait-post-* and
  deny-post-{access|maxmailsize|tocc|subonlypost|modonlypost})
  the email message being processed (usually a mail being moderated); N
  represents a number, which is how many lines of the message (including
  headers) to include: if omitted, the whole message will be included

- %digestthreads%
  (available only in digest)
  the list of threads included in the digest

- %gatekeepers%
  (available only in gatekeep-sub and wait-sub)
  the list of moderators to whom the moderation request has been sent

- %listsubs%
  (available only in list---*)
  the list of normal subscribers
  DEPRECATED: use %normalsubs%

- %normalsubs%
  (available only in list---*)
  the list of normal subscribers

- %digestsubs%
  (available only in list---*)
  the list of digest subscribers

- %nomailsubs%
  (available only in list---*)
  the list of nomail subscribers

- %moderators%
  (available only in moderate-post-* and wait-post-*)
  the list of moderators to whom the moderation request has been sent

- %bouncenumbers%
  (available only in probe)
  the list of indexes of messages which may not have been received as they
  bounced

Directives which include a list of items have the behaviour that each item is
preceded and followed by the same text as preceded and followed the directive
on its line; only one such directive is supported per line. Those which include
a block of text have the behaviour that second and later lines are preceded
with as many spaces as there were bytes preceding the directive; any text
following such directives on the same line is omitted.

If a line with any of these directives, after processing, contains only
whitespace, the line does not appear at all in the output (the newline and any
whitespace is omitted).

Unformatted substitutions
-------------------------

Unformatted substitutions that are available are:

- $bouncenumbers$
  (available only in probe)
  the formatted list of indexes of messages which may not have been received as
  they bounced
  DEPRECATED: use %bouncenumbers%

- $confaddr$
- $confirmaddr$
  (available only in confirm-[un]sub-*)
  the address to which to send mail to confirm the (un-)subscription in
  question
  NOTE: the short version of this substitution is DEPRECATED

- $control C$
  the contents of the control file named C in listdir/control, with its final
  newline stripped; the name may only include letters, digits, underscore, dot
  and hyphen, and may not start with a dot; note that there is a formatted
  version of this directive

- $digestfirst$
  (available only in digest)
  index of the first message included in a digest

- $digestinterval$
  (available only in digest)
  indexes of the first and last messages included in a digest (e.g. 1-5), or
  just the index if only a single message is included

- $digestissue$
  (available only in digest)
  the issue number of the digest

- $digestlast$
  (available only in digest)
  index of the last message included in a digest

- $digestsubaddr$
  listname+subscribe-digest@domain.tld
  DEPRECATED: use $list+$subscribe-digest@$domain$ instead

- $digestsubreleaseaddr$
  (available only in deny-post-subonlypost and wait-post-modnonsubposts)
  the address to which to send mail to simultaneously subscribe to the digest
  version of the list and release the post in question

- $digestthreads$
  (available only in digest)
  the formatted list of threads included in the digest
  DEPRECATED: use %digestthreads%

- $digestunsubaddr$
  listname+unsubscribe-digest@domain.tld
  DEPRECATED: use $list+$unsubscribe-digest@$domain$ instead

- $domain$
  domain.tld

- $faqaddr$
  listname+faq@domain.tld
  DEPRECATED: use $list+$faq@$domain$ instead

- $helpaddr$
  listname+help@domain.tld
  DEPRECATED: use $list+$help@$domain$ instead

- $list$
  listname

- $list+$
  listname+

- $listaddr$
  listname@domain.tld
  DEPRECATED: use $list$@$domain$ instead

- $listgetN$
  listname+get-N@domain.tld
  (the N here is nothing special, so this won't actually work, but is used to
  explain to users how to use the +get functionality)
  DEPRECATED: use $list+$get-N@$domain$ instead

- $listowner$
  listname+owner@domain.tld
  DEPRECATED: use $list+$owner@$domain$ instead

- $listsubaddr$
  listname+subscribe@domain.tld
  DEPRECATED: use $list+$subscribe@$domain$ instead

- $listsubreleaseaddr$
  (available only in deny-post-subonlypost and wait-post-modnonsubposts)
  the address to which to send mail to simultaneously subscribe to the list
  and release the post in question

- $listunsubaddr$
  listname+unsubscribe@domain.tld
  DEPRECATED: use $list+$unsubscribe@$domain$ instead

- $maxmailsize$
  (available only in deny-post-maxmailsize)
  the maximum size of mail that Mlmmj will accept

- $moderateaddr$
  (available only in moderate-post-* and gatekeep-sub)
  the address to which to send mail to approve the post or subscription in
  question
  DEPRECATED: use $releaseaddr$ or $permitaddr$ instead

- $moderators$
  (available only in moderate-post-*, wait-post-*, gatekeep-sub and wait-sub)
  the formatted list of moderators to whom the moderation request has been sent
  DEPRECATED: use %moderators% or %gatekeepers% instead

- $newsub$
  (available only in notify-sub-*-*)
  the address that has been subscribed
  DEPRECATED: use $subaddr$ instead

- $nomailsubaddr$
  listname+subscribe-nomail@domain.tld
  DEPRECATED: use $list+$subscribe-nomail@$domain$ instead

- $nomailsubreleaseaddr$
  (available only in deny-post-subonlypost and wait-post-modnonsubposts)
  the address to which to send mail to simultaneously subscribe to the no-mail
  version of the list and release the post in question

- $nomailunsubaddr$
  listname+unsubscribe-nomail@domain.tld
  DEPRECATED: use $list+$unsubscribe-nomail@$domain$ instead

- $oldsub$
  (available only in notify-sub-*-*)
  the address that has been unsubscribed
  DEPRECATED: use $subaddr$ instead

- $originalmail$
  the same as %originalmail 100% preceded by a space
  DEPRECATED: use %originalmail%

- $permitaddr$
  (available only in gatekeep-sub)
  the address to which to send mail to permit the subscription in question

- $posteraddr$
  (available only in deny-post-{access|tocc|subonlypost|modonlypost|
  maxmailsize}, moderate-post-* and wait-post-*)
  the from address of the message that was received as determined by Mlmmj

- $random0$
- $random1$
- $random2$
- $random3$
- $random4$
- $random5$
  these are 6 distinct random strings; they allow list texts to be constructed
  that are MIME messages with attachments by creating boundaries that are
  unlikely to appear in the attached messages

- $releaseaddr$
  (available only in moderate-post-*)
  the address to which to send mail to release the post in question

- $subaddr$
  (available only in gatekeep-sub, confirm-[un]sub-*, finish-[un]sub-*,
  notify-[un]sub-* and deny-[un]sub-*)
  the address requested to be (un-)subscribed

- $subject$
  (available only in deny-post-{access|tocc|subonlypost|modonlypost|
  maxmailsize}, moderate-post-* and wait-post-*)
  the subject line of the message in question

- $text T$
  text from the file named T in the listdir/text directory, with its final
  newline stripped; the name may only include letters, digits, underscore, dot
  and hyphen, and may not start with a dot; note that there is a formatted
  version of this directive

Escapes
-------

These allow you to avoid special meanings of characters used for other purposes
in list texts, as well as control the construction of the texts at a fairly low
level.

- $$
  a single $

- %%
  a single %

- \\
  a single \

- \uNNNN
  (NNNN represents four hex digits)
  a Unicode character
  (this is not really appropriate for use in a header, except perhaps the
  Subject: header as Mlmmj does automatic quoting for that header as described
  above)

- \<space>
  a space, but don't allow the line to be broken here when wrapping

- \/
  nothing, but allow the line to be broken here when wrapping

- \=
  nothing, but don't allow the line to be broken here when wrapping

