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 / 16GB
  • CPU: 1/4/4
  • storage (add some more to grow):
    • database: ~50-100MB / ~5GB / ~15GB
    • mailman data: ~10-50MB / 2GB / 5GB
    • search index: 10-100MB / ~25GB / ~80-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.

user and pwd variables shall contain the Mailman REST API credentials.

Adding DMARC mitigation

This script first display the original value for each list and then enable DMARC “munging” mitigation (recommanded).

from mailmanclient import Client
client = Client('http://localhost:8001/3.1', user, pwd)

d = client.get_domain('')

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

for ml in d.lists:
  ml.settings['dmarc_mitigate_action'] = 'munge_from'

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:

from mailmanclient import Client
client = Client('http://localhost:8001/3.1', user, pwd)

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: