This documentation focuses on the deployment of the Mailman 3 suite (core daemon, Hyperkitty and Postorius UIs).


Mailman 3 requires (respectively for a small instance, a big one like, and a huge one like (estimated, WIP)):

  • RAM: at least 2GB / 9GB / 17GB
  • CPU: 1/4/4
  • storage (add some more to grow):
    • database: ~50-100MB / ~5GB / ~15GB
    • mailman data: ~10-50MB / 2-3GB / 150M-5GB
    • search index: 10-100MB / ~25GB / ~60-90GB
    • Mailman 2 archives (for migration and to keep the old URLs alive, thus permanent): ~100MB / 10GB / ~150GB
  • swap: 1GB should be enough


Using the mailing-lists-server super-role it is very easy to deploy a ML instance:

- hosts:
  - role: swap_file
    size: 1G
    path: /var/swap
  - role: mailing-lists-server
    display_name: "The Open Data Hub List Archives"
      - duck
      - misc
      root: "{{ ['root'] + comminfra_tech_emails }}"
      listmaster: root
    use_simple_tls: True
  tags: mailinglists

Then if you want to migration from Mailman 2 then please follow the Migration Notes. You can add this to the playbook to make the old archives available at the old URLs:

    - name: "Configure web access to old ML archives"
        src: "{{ data_dir }}/old_ml_archives.conf"
        dest: "{{ _vhost_confdir }}/"
        owner: root
        group: root
        mode: 0644
      notify: reload httpd

With old_ml_archives.conf containing:

# Ansible managed

Alias "/pipermail" "/srv/data/mailman2/archives/public"
<Directory "/srv/data/mailman2/archives/public">
    Require all granted
    Options Indexes SymLinksIfOwnerMatch
    IndexIgnore .??*
    IndexOptions FancyIndexing HTMLTable IconsAreLinks SuppressSize SuppressDescription NameWidth=*

RedirectMatch ^/mailman/listinfo/(.*) /archives/list/$
RedirectMatch ^/mailman.* /archives/

Log into the UI and create your admin account, then rerun the playbook to get elevated to administrator of the instance (you need to have set admin_users properly with your user handle, but that can be done now).


Email Templates

New templates can be installed in /var/lib/mailman3/templates/ in the site/<lang> subdirectory for global templates and lists/<list-name>/<lang> subdirectory for per-lists templates. The list-name is the list address with the @ replaced by a dot.

Custom Assets

Custom logo, images, favicon or CSS can be stored in {{ webapp_path }}/static-extra/.

Custom Content

Hyperkitty pages can be customized by overriding Django templates into {{ webapp_path }}/templates/hyperkitty/.

For example the navbar-brand.html template can be overriden to add you own logo or alter the top bar visual:

{% load static from staticfiles %}
<a class="navbar-brand" href="{% url 'hk_root' %}" title="{{ site_name }}">
    <img alt="{{ site_name|title }}" src="{% static 'my_custom_logo.png' %}" style="float: left; margin-right: 30px; height: 60px; padding: 0; margin-top: -20px;" />
    {{ site_name }}

It is possible to add specific headers, which can be used to inject your specific theme CSS, by overriding headers.html:

{% load static from staticfiles %}
    <link rel="stylesheet" href="{% static 'my_custom_theme.css' %}" type="text/css" media="all" >

Automation using the Python Console

These are examples to show what’s possible to make mass modifications without editing each list one by one in the UI.

These scripts needs to be run under Python 2 and require the following extra dependencies:

  • python-configparser (to be able to parse the configuration)

Modification of list parameters

Method to display and modify a parameter

This script first display the original value of the key for each list and then modify the value:

import configparser
config = configparser.ConfigParser()'/etc/mailman.cfg')

from mailmanclient import Client
client = Client('http://localhost:8001/3.1', config['webservice']['admin_user'], config['webservice']['admin_pass'])

d = client.get_domain('')

for ml in d.lists:
  print("{}: {}".format(ml.list_name, ml.settings['key']))

for ml in d.lists:
  ml.settings['key'] = 'value'

Useful parameters not available in the UI (at least in our version)

  • Adding DMARC mitigation: update dmarc_mitigate_action to munge_from
  • Get rid of Uncaught bounce notification messages: update forward_unrecognized_bounces_to to 0 in our version it is not yet exposed in the API, instead use the following SQL statement on the mailman database: UPDATE mailinglist SET forward_unrecognized_bounces_to=0;

Set List to be moderated by default

This script ensures that all new list members are moderated, but allows existing members to skip the moderation:

import configparser
config = configparser.ConfigParser()'/etc/mailman.cfg')

from mailmanclient import Client
client = Client('http://localhost:8001/3.1', config['webservice']['admin_user'], config['webservice']['admin_pass'])

ml = client.get_list('')

# ensure previous members are not moderated, unless explicitely set
for mem in ml.members:
  if not mem.moderation_action:
    mem.moderation_action = 'defer'

# change the ML default processing for members to moderation
ml.settings['default_member_action'] = 'hold'

List moderators can use this setting to filter out trolls. Once a person has posted several valuable messages then the moderators can disable moderation for this specific user in the UI.

Known problems and limitations

Hyperkitty is not at the latest version

Previously the packaging was kindly done my Aurelien Bompard, but he now lacks time to maintain it. We decided to take over and try to help push the various bits to be one day available in Fedora and later RHEL 9.

We ported the latest version of Mailman 3 Core (the server routing the messages) for EL7 to get various fixes but the UI is blocked at and older version as it requires a more recent Python version, many many dependencies, and various patches. Aside from fixes we have no plan to deliver newer version for EL7 (read below).

We are currently working on packaging the lastest version of the whole suite into Fedora as well as EPEL8. The goal is to move our work into official repositories.

Admin settings

A few list and site-wide settings are not yet available in the web UI (global bans, maybe others).

Member options appear not visible when the user has not made a choice and global defaults are in use.

Cannot delete list archive

Deleting a list removes the configuration in the routing daemon, thus it is not possible anymore to post, and the archives are kept (read-only). This is usually what most people want but sometimes you made a mistake or wish to get rid of some tests but unfortunately it is not possible to remove archives in the web UI.

With shell access it is possible though, with this procedure:

cd /var/www/mailman/config/
import django
from hyperkitty.models import MailingList
ml = MailingList.objects.get(name="<list-email>")




Ansible roles: