Templates
In Mailman 3.1 a new template system was introduced to allow for maximum flexibility in the format and content of messages sent by and through Mailman. For example, when a new member joins a list, a welcome message is sent to that member. The welcome message is created from a template found by a URL associated with a template name and a context.
So if for example, you want to include links to pages on your website, you can create a custom template, make it available via download from a URL, and then associate that URL with a mailing list’s welcome message. Some standard placeholders can be defined in the template, and these will be filled in by Mailman when the welcome message is sent.
The URL itself can have placeholders, and this allows for additional flexibility when looking up the content.
Examples
Let’s say you have a mailing list:
>>> from mailman.app.lifecycle import create_list
>>> ant = create_list('ant@example.com')
The standard welcome message doesn’t have any links to it because by default Mailman doesn’t know about any web user interface front-end. When Anne is subscribed to the mailing list, she sees this plain welcome message.
>>> from mailman.testing.helpers import subscribe
>>> from mailman.testing.helpers import get_queue_messages
>>> anne = subscribe(ant, 'Anne')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Subject: Welcome to the "Ant" mailing list
From: ant-request@example.com
To: Anne Person <aperson@example.com>
...
Welcome to the "Ant" mailing list!
To post to this list, send your message to:
ant@example.com
You can unsubscribe or make adjustments to your options via email by
sending a message to:
ant-request@example.com
with the word 'help' in the subject or body (don't include the
quotes), and you will get back a message with instructions. You will
need your password to change your options, but for security purposes,
this password is not included here. If you have forgotten your
password you will need to reset it via the web UI.
Let’s say though that you wanted to provide a link to a Code of Conduct in the welcome message. You publish both the code of conduct and the welcome message pointing to the code on your website. Now you can tell the mailing list to use this welcome message instead of the default one.
>>> from mailman.testing.documentation import call_http
>>> call_http('http://localhost:9001/3.1/lists/ant.example.com/uris', {
... 'list:user:notice:welcome': 'http://localhost:8180/welcome_1.txt',
... }, method='PATCH')
date: ...
server: ...
status: 204
The name of the template corresponding to the welcome message is list:user:notice:welcome and the location of your new welcome message text is at http://localhost:8180/welcome_1.txt.
Now when a new member subscribes to the mailing list, they’ll see the new welcome message.
>>> bill = subscribe(ant, 'Bill')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Subject: Welcome to the "Ant" mailing list
From: ant-request@example.com
To: Bill Person <bperson@example.com>
...
Welcome to the "Ant" mailing list!
To post to this list, send your email to:
ant@example.com
There is a Code of Conduct for this mailing list which you can view at
http://www.example.com/code-of-conduct.html
It’s even possible to require a username and password (Basic Auth) for retrieving the welcome message.
>>> call_http('http://localhost:9001/3.1/lists/ant.example.com/uris', {
... 'list:user:notice:welcome': 'http://localhost:8180/welcome_2.txt',
... 'username': 'anne',
... 'password': 'is special',
... }, method='PATCH')
date: ...
server: ...
status: 204
The username and password will be used to retrieve the welcome text.
>>> cris = subscribe(ant, 'Cris')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Subject: Welcome to the "Ant" mailing list
From: ant-request@example.com
To: Cris Person <cperson@example.com>
...
I'm glad you made it!
The text is cached so subsequent uses don’t necessarily need to hit the internet.
>>> dave = subscribe(ant, 'Dave')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Subject: Welcome to the "Ant" mailing list
From: ant-request@example.com
To: Dave Person <dperson@example.com>
...
I'm glad you made it!
Template format
Mailman expects the templates to be return as content type text/plain; charset=”UTF-8”.
Template URLs can be any of the following schemes:
http:// - standard scheme supported by the requests library;
https:// - standard scheme also supported by requests;
file:/// - any path on the local file system; UTF-8 contents by default;
mailman:/// - a path defined within the Mailman source code tree. It is not recommended that you use these; they are primarily provided for Mailman’s internal use.
Generally, if a template is not defined or not found, the empty string is used. IOW, a missing template does not cause an error, it simply causes the named template to be blank.
Line wrapping
Many, but not all, templates have their text wrapped at column 70. This can result in a nicer looking result but can also break long URLs. To avoid this, you can indent any lines you don’t want wrapped by one or more spaces which will inhibit wrapping of those lines. Here are some examples.
>>> text = """Here's some sample text
... that should be wrapped and filled to make a pretty looking paragraph of text with no excessively short
... or long lines, but it also contains a long url.
...
... https://www.example.com/mailman3/lists/mailman-users.mailman3.org/members/options/user@example.org?role=member
...
... which shouldn't be wrapped.
... """
The normal template wrapping process produces
>>> from mailman.utilities.string import wrap
>>> print(wrap(text))
Here's some sample text that should be wrapped and filled to make a
pretty looking paragraph of text with no excessively short or long
lines, but it also contains a long url.
https://www.example.com/mailman3/lists/mailman-users.mailman3.org/memb
ers/options/user@example.org?role=member
which shouldn't be wrapped.
We see the URL is wrapped and we don’t want that so we insert a leading blank.
>>> import re
>>> text = re.sub('\nhttps', '\n https', text)
>>> print(wrap(text))
Here's some sample text that should be wrapped and filled to make a
pretty looking paragraph of text with no excessively short or long
lines, but it also contains a long url.
https://www.example.com/mailman3/lists/mailman-users.mailman3.org/members/options/user@example.org?role=member
which shouldn't be wrapped.
URL placeholders
The URLs themselves can contain placeholders, and this can be used to provide even more flexibility in the way the template texts are retrieved. Two common placeholders include the List-ID and the mailing list’s preferred language code.
>>> ant.preferred_language = 'fr'
>>> call_http('http://localhost:9001/3.1/lists/ant.example.com/uris', {
... 'list:user:notice:welcome':
... 'http://localhost:8180/$list_id/$language/welcome_3.txt',
... }, method='PATCH')
date: ...
server: ...
status: 204
The next person to subscribe will get a French welcome message.
>>> dave = subscribe(ant, 'Elle')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
Subject: =?iso-8859-1?q?Bienvenue_sur_la_liste_de_diffusion_=AB_Ant_=BB?=
From: ant-request@example.com
To: Elle Person <eperson@example.com>
...
Je suis heureux que vous pouvez nous rejoindre!
Standard URL substitutions include:
$list_id - The mailing list’s List-ID (ant.example.com)
$listname - The mailing list’s fully qualified list name (ant@example.com)
$domain_name - The mailing list’s domain name (example.com)
$language - The language code for the mailing list’s preferred language (fr)
Template contexts
When Mailman is looking for a template, it always searches for it in up to three contexts, and you can set the template for any of these three contexts: a mailing list, a domain, the site.
Most templates are searched first by the mailing list, then by domain, then by
site. One notable exception is the domain:admin:notice:new-list
template,
which is sent when a new mailing list is created. Because (modulo any style
default settings) there won’t be a template for the newly created mailing
list, this template is always searched for first in the domain, and then in
the site.
In fact, this illustrates a common naming scheme for templates. The
colon-separated sections usually follow the form
<context>:<recipient>:<type>:<name>
where context
would be “domain” or
“list, <recipient>
would be “admin”, “user”, or “member”, and <type>
can be “action” or “notice”. This isn’t a strict naming scheme, but it does
give you some indication as to the use of the template. All template names
used internally by Mailman are given below.
You’ve already seen how the mailing list context works above. Let’s look at the domain and site contexts next.
Domain context
Let’s say you want all mailing lists in a given domain to share exactly the same welcome message template. Remember that Mailman will insert substitutions into the templates themselves to customize them for each mailing list, so in general a single template can be shared by all mailing lists in the domain.
The first thing to do is to set the URI for the welcome message in the domain to be shared.
>>> call_http('http://localhost:9001/3.1/domains/example.com/uris', {
... 'list:user:notice:welcome':
... 'http://localhost:8180/welcome_4.txt',
... }, method='PATCH')
date: ...
server: ...
status: 204
And let’s create a new mailing list in this domain.
>>> bee = create_list('bee@example.com')
Now when Anne subscribes to the Bee mailing list, she will get this domain-wide welcome message.
>>> anne = subscribe(bee, 'Anne')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Subject: Welcome to the "Bee" mailing list
From: bee-request@example.com
To: Anne Person <aperson@example.com>
...
Welcome to the Bee list in the example.com domain.
So far so good. What happens if Fred subscribes to the Ant mailing list?
>>> fred = subscribe(ant, 'Fred')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
Subject: =?iso-8859-1?q?Bienvenue_sur_la_liste_de_diffusion_=AB_Ant_=BB?=
From: ant-request@example.com
To: Fred Person <fperson@example.com>
...
Je suis heureux que vous pouvez nous rejoindre!
Okay, that’s strange! Why did Fred get the French welcome message? It’s because the mailing list context overrides the domain context! Similarly, a domain context overrides a site context. This allows you to provide generic templates to be used as a default, with specific overrides where necessary.
Let’s delete the Ant list’s override.
>>> ant.preferred_language = 'en'
>>> call_http('http://localhost:9001/3.1/lists/ant.example.com/uris'
... '/list:user:notice:welcome',
... method='DELETE')
date: ...
server: ...
status: 204
Now when Gwen subscribes to the Ant list, she gets the domain’s welcome message.
>>> gwen = subscribe(ant, 'Gwen')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Subject: Welcome to the "Ant" mailing list
From: ant-request@example.com
To: Gwen Person <gperson@example.com>
...
Welcome to the Ant list in the example.com domain.
Site context
Let’s say we want the same welcome template for every mailing list on our Mailman installation. For this we use the site context.
First, let’s delete the domain context we set previously. Note that previously we used a DELETE method on the list’s welcome template resource, but we could have also done this by PATCHing an empty string for the URI, which Mailman’s REST API interprets as a deletion too. Let’s use this approach to delete the domain welcome message.
>>> call_http('http://localhost:9001/3.1/domains/example.com/uris', {
... 'list:user:notice:welcome': '',
... }, method='PATCH')
date: ...
server: ...
status: 204
Now let’s set a new welcome template URI for the site.
>>> call_http('http://localhost:9001/3.1/uris', {
... 'list:user:notice:welcome':
... 'http://localhost:8180/welcome_5.txt',
... }, method='PATCH')
date: ...
server: ...
status: 204
Now Herb subscribes to both the Ant…
>>> herb = subscribe(ant, 'Herb')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Subject: Welcome to the "Ant" mailing list
From: ant-request@example.com
To: Herb Person <hperson@example.com>
...
Yay! You joined the ant@example.com mailing list.
…and Bee mailing lists.
>>> herb = subscribe(bee, 'Herb')
>>> items = get_queue_messages('virgin')
>>> print(items[0].msg)
MIME-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Subject: Welcome to the "Bee" mailing list
From: bee-request@example.com
To: Herb Person <hperson@example.com>
...
Yay! You joined the bee@example.com mailing list.
Templated texts
All the texts that Mailman uses to create or decorate messages can be associated with a URL. Mailman looks up templates by name and downloads it via that URL. The retrieved text supports placeholders which are filled in by Mailman. There are a common set of placeholders most templates support:
listname
- fully qualified list name (e.g.ant@example.com
)list_id
- theList-ID
header (e.g.ant.example.com
)display_name
- the display name of the mailing list (e.g.Ant
)short_listname
- the local part of the list name (e.g.ant
)domain
- the domain name part of the list name (e.g.example.com
)description
- the mailing list’s short description textinfo
- the mailing list’s longer descriptive textrequest_email
- the email address for the-request
aliasowner_email
- the email address for the-owner
aliassite_email
- the email address to reach the owners of the sitelanguage
- the two letter language code for the list’s preferred language (e.g.en
,it
,fr
)
Along with these, Mailman supports placeholder as URLs for web interface
when base_url
property is set at the Domain
level. These include:
domain_url
- URL to homepage for a domain.mailinglist_url
- URL to homepage for a mailinglist.held_message_url
- URL to page for handling all held messages.pending_subscriptions_url
- URL to page for handling pending subscription requests.pending_unsubscriptions_url
- URL to page for handling pending unsubscriptions.
Other template substitutions are described below the template name listed below. Here are all the supported template names:
domain:admin:notice:new-list
Sent to the administrators of any newly created mailing list.
list:admin:action:post
Sent to the list administrators when moderator approval for a posting is required.
subject
- the originalSubject
of the messagesender_email
- the poster’s email addressreasons
- some reasons why the post is being held for approval
list:admin:action:subscribe
Sent to the list administrators when moderator approval for a subscription request is required.
member
- display name and email address of the subscriber
list:admin:action:unsubscribe
Sent to the list administrators when moderator approval for an unsubscription request is required.
member
- display name and email address of the subscriber
list:admin:notice:disable
Sent to the list administrators to notify them when a member’s delivery is disabled due to excessive bounces.
member
- display name and email address of the subscriber
list:admin:notice:increment
When configured, sent to the list administrators to notify them when a member’s bounce score is incremented.
member
- display name and email address of the subscriber
list:admin:notice:pending
The notice of pending moderator requests sent to the list administrators by the
mailman notify
command.count
- the number of pending requests.data
- the list of pending requests.
list:admin:notice:removal
Sent to the list administrators to notify them when a member is unsubscribed from am mailing list due to excessive bounces.
member
- display name and email address of the subscriber
list:admin:notice:subscribe
Sent to the list administrators to notify them when a new member has been subscribed.
member
- display name and email address of the subscriber
list:admin:notice:unrecognized
Sent to the list administrators when a bounce message in an unrecognized format has been received.
list:admin:notice:unsubscribe
Sent to the list administrators to notify them when a member has been unsubscribed.
member
- display name and email address of the subscriber
list:member:digest:footer
The footer for a digest message.
list:member:digest:header
The header for a digest message.
list:member:digest:masthead
The digest “masthead”; i.e. a common introduction for all digest messages.
list:member:regular:footer
The footer for a regular (non-digest) message.
<archiver_name>_url
- a link to the archived message for each enabled archiver other than prototype. For example if the HyperKitty archiver is enabled for the list,${hyperkitty_url}
will point to the message in HyperKitty.
When personalized deliveries are enabled, these substitution variables are also defined:
member
- display name and email address of the subscriberuser_email
- the email address of the recipientuser_delivered_to
- the case-preserved email address of the recipientuser_language
- the description of the user’s preferred language (e.g. “French”, “English”, “Italian”)user_name
- the recipient’s display name if availableuser_name_or_email
- the recipient’s display name if available, or their email address if no display name (e.g. “Anne Person”, “Bart”, or “fperson@example.com”)
list:member:regular:header
The header for a regular (non-digest) message.
<archiver_name>_url
- a link to the archived message for each enabled archiver other than prototype. For example if the HyperKitty archiver is enabled for the list,${hyperkitty_url}
will point to the message in HyperKitty.
When personalized deliveries are enabled, these substitution variables are also defined:
member
- display name and email address of the subscriberuser_email
- the email address of the recipientuser_delivered_to
- the case-preserved email address of the recipientuser_language
- the description of the user’s preferred language (e.g. “French”, “English”, “Italian”)user_name
- the recipient’s display name if availableuser_name_or_email
- the recipient’s display name if available, or their email address if no display name (e.g. “Anne Person”, “Bart”, or “fperson@example.com”)
list:user:action:invite
The message sent to subscribers when they are invited to join a List.
user_email
- the email address being invited.token
- the unique confirmation token
list:user:action:subscribe
The message sent to subscribers when a subscription confirmation is required.
token
- the unique confirmation tokensubject
- theSubject
heading for the confirmation email, which includes the confirmation tokenconfirm_email
- the email address to send the confirmation response to; this corresponds to theReply-To
headeruser_email
- the email address being confirmed
list:user:action:unsubscribe
The message sent to subscribers when an unsubscription confirmation is required.
token
- the unique confirmation tokensubject
- theSubject
heading for the confirmation email, which includes the confirmation tokenconfirm_email
- the email address to send the confirmation response to; this corresponds to theReply-To
headeruser_email
- the email address being confirmed
list:user:notice:goodbye
The notice sent to a member when they unsubscribe from a mailing list.
user_email
- the email address of the unsubscribing member
list:user:notice:hold
The notice sent to a poster when their message is being held or moderator approval.
subject
- the originalSubject
of the messagesender_email
- the poster’s email addressreasons
- some reasons why the post is being held for approval
list:user:notice:no-more-today
Sent to a user when the maximum number of autoresponses has been reached for that day.
sender_email
- the email address of the postercount
- the number of autoresponse messages sent to the user today
list:user:notice:post
Notice sent to a poster when their message has been received by the mailing list.
subject
- theSubject
field of the received message
list:user:notice:probe
A bounce probe sent to a member when their subscription has been disabled due to bounces.
sender_email
- the email address of the bouncing member
list:user:notice:refuse
Notice sent to a poster when their message has been rejected by the list’s moderator.
request
- the type of request being rejectedreason
- the reason for the rejection, as provided by the list’s moderators
list:user:notice:rejected
Notice sent to a poster when their message has been automatically rejected.
reasons
- some reasons why the post was rejected
list:user:notice:warning
The notice sent to a member when their membership has been disabled due to excessive bounces.
user_email
- the email address of the bouncing member.
list:user:notice:welcome
The notice sent to a member when they are subscribed to the mailing list.
user_name
- the display name of the new memberuser_email
- the email address of the new member