Installing Mailman3 in a qmail + vpopmail server

June 8, 2024 by Roberto Puzzanghera 0 comments

Mailman is free software for managing electronic mail discussion and e-newsletter lists. Mailman is integrated with the web, making it easy for users to manage their accounts and for list owners to administer their lists. Mailman supports built-in archiving, automatic bounce processing, content filtering, digest delivery, spam filters, and more.

Mailman is free software, distributed under the GNU General Public License, and written in the Python programming language.

Index


If you are looking for a more complete and advanced mailing list software than ezmlm, Mailman is definitely one of the best options.

It seems that there are many different guides even in the official documentation and it's not easy to find the best starting point at the beginning.

This is the one that worked for me (I'll add further links to the documentation during the next steps):

Preparing the virtual environment

The venv module supports creating lightweight “virtual environments”, each with their own independent set of Python packages installed in their site directories. A virtual environment is created on top of an existing Python installation, known as the virtual environment’s “base” Python, and may optionally be isolated from the packages in the base environment, so only those explicitly installed in the virtual environment are available.

Add the mailman user:

groupadd mailman
useradd -g mailman -d /usr/local/mailman mailman
mkdir /usr/local/mailman
chown -R mailman:mailman /usr/local/mailman

The mailman user's home directory /usr/local/mailman is the container where the virtual environment that we are going to build will live.

The creation of the virtual environment  must be done as the mailman user:

su - mailman

First of all create the .profile file for the mailman user, so that the next time you call the pip binary it will be taken from its virtual environment (run the same from the command line or exit from the shell):

echo "export PATH=~/bin:\$PATH" > .profile

Prepare the virtual environment:

python3 -m venv --upgrade-deps /usr/local/mailman

This will create the virtual environment in /usr/local/mailman. Always as the mailman user activate the virtual environment:

cd
source bin/activate

Your command prompt will slightly change its appearence to inform that you entered the virtual environment.

(mailman) mailman@qmail:~$

Activating the virtual environment will prepend the /usr/local/mailman directory to your PATH, so that running python will invoke the environment’s python interpreter and you can run installed scripts without having to use their full path.

Be aware that you have to activate the virtual environment each time you do an (un)installation/upgrade of a python package. To deactivate just run the deactivate command.

The rest of this documentation mostly assumes that the virtual environment is activated. Whether or not a certain command needs that the virtual environment is activated can be seen by a (mailman) before the $ shell prompt. It is also assumed that you activate the virtual environment as the mailman user.

Installing Mailman core

Do not exit from the virtual environment as we have to do the Mailman core installation from the inside using the pip command, which is a copy of the pip3 in our virtual environment.

(mailman)$ pip install mailman

This command installs Mailman core with all its prerequisites in the virtual environment. If everything is ok it exits with a "Successfully installed" verb followed by the list of the packages installed.

Typing mailman info will show the details and version of the installed Mailman.

Configuration

By default Mailman will look for its configuration file in the ~/var/etc directory. Since I prefer to use ~/etc (also for other config files that have to come later), some adjustment is needed. We have to define the environment variable MAILMAN_CONFIG_FILE in the .profile file:

su - mailman
source bin/activate
(mailman)$ cat >> ~/.profile << __EOF__
export MAILMAN_CONFIG_FILE=~/etc/mailman.cfg
export PYTHONPATH=~/etc
export PYTHONHOME=~/
__EOF__

Create the etc directory

(mailman)$ mkdir -p ~/etc

Now edit the ~/etc/mailman.cfg, which overwrites the settings shown in schema.cfg.

(mailman)$ cat >> ~/etc/mailman.cfg << __EOF__
[devmode] 
enabled: no 

[mailman] 
# This address is the "site owner" address.  Certain messages which must be 
# delivered to a human, but which can't be delivered to a list owner (e.g. a 
# bounce from a list owner), will be sent to this address.  It should point to 
# a human. 
site_owner: postmaster@mydomain.tld 

# The default language for this server. 
default_language: en 

[paths.master] 
var_dir: /usr/local/mailman/var 

[logging.database] 
level: warn 

[logging.debug] 
path: debug.log 
level: warn 

[logging.http] 
level: warn 

[logging.smtp] 
path: smtp.log 
level: info 

[language.it] 
description: Italian 
charset: utf-8 
enabled: yes

[mta] 
# https://docs.mailman3.org/projects/mailman/en/latest/src/mailman/docs/mta.html#qmail 
# 
# NullMTA is just implementing the interface and thus satisfying Mailman 
# without doing anything fancy 
incoming: mailman.mta.null.NullMTA 
# Mailman should not be run as root. 
# Use any convenient port > 1024.  8024 is a convention, but can be 
# changed if there is a conflict with other software using that port. 
lmtp_host: 127.0.0.1 
lmtp_port: 8024 
# This will listen on localhost:8024 with LMTP and deliver outgoing messages to localhost:25. 

# How to connect to the outgoing MTA.  If smtp_user and smtp_pass is given, 
# then Mailman will attempt to log into the MTA when making a new connection. 
#smtp_host: 10.0.0.4

# Some list posts and mail to the -owner address may contain DomainKey or 
# DomainKeys Identified Mail (DKIM) signature headers <http://www.dkim.org/>. 
# Various list transformations to the message such as adding a list header or 
# footer or scrubbing attachments or even reply-to munging can break these 
# signatures.  It is generally felt that these signatures have value, even if 
# broken and even if the outgoing message is resigned.  However, some sites 
# may wish to remove these headers by setting this to 'yes'. 
remove_dkim_headers: no
__EOF__

Adjust the preferred language and the site owner's email to your needs.

Secure mailman.cfg as it has to store the database password:

chmod o-r ~/etc/mailman.cfg

Hooking up qmail to work with Mailman

The following commands must be run as root, so exit from the mailman shell (exit).

We'll host the lists on a virtual domain, let's say lists.mydomain.tld (if you want to use other domains as well or ordinary virtual domains for your lists read below towards the end of this article), and use the /var/qmail/control/virtualdomains file to put the mailman user in charge of this virtual domain:

echo lists.mydomain.tld:mailman >> /var/qmail/control/virtualdomains

Be aware that this virtual domain should not be created by the usual vadddomain program, as it exists just to bind the mailman user to the lists.mydomain.tld domain and its definition is different from the vpopmail's ordinary domains. In this context, mailman is a qmail/vpopmail user which has nothing to do with its counterpart in the Linux user space.

Make also sure to setup SPF, DKIM and DMARC for lists.mydomain.tld.

Add lists.mydomain.tld to rcpthosts so that qmail-smtpd will accept mails for that domain. Do not add it to control/locals otherwise the virtualdomains file will be ignored and the  Mailman program won't be called.

echo lists.mydomain.tld >> /var/qmail/control/rcpthosts

The settings to make Mailman work with qmail are already in the mailman.cfg that we created above:

[mta]
# NullMTA is just implementing the interface and thus satisfying Mailman 
# without doing anything fancy. Default would be postfix otherwise.
incoming: mailman.mta.null.NullMTA 

# Use any convenient port > 1024.  8024 is a convention, but can be 
# changed if there is a conflict with other software using that port.
# This will listen on localhost:8024 with LMTP and deliver outgoing messages
# to localhost:25. 
lmtp_port: 8024

The qmail-lmtp script in the contrib directory is used to tell Mailman how many parts (separated by dashes) of the destination address to filter out. We'll not use the one available in the contrib directory because it doesn't work with mailman3, as explained here, where I found a patch to cure the problem. In fact, qmail-local sends bare LF for the end line, while the SMTP standards require CRLF, so the SMTP conversation between qmail and Mailman ends up with the error: "Line too long (see RFC5321 4.5.3.1.6)". According to what a developer said the patched qmail-lmtp file that we are going to install can have unpredictable results when binary files with \n and \r characters are sent (that would need qmail-local to be patched). Anyway it works fine here with my lists, although I've never tested them with binary files.

(mailman)$ wget -O ~/qmail-lmtp https://notes.sagredo.eu/files/qmail/mailman/qmail-lmtp

Now insert the call to that script in the default dot-qmail file

(mailman)$ echo "|~/qmail-lmtp 8024 1" > ~/.qmail-default

The first argument specifies the LMTP port of Mailman.

Since inbound messages are delivered by vpopmail to the user mailman, it's necessary to allow it to access its home directory ~mailman. In particular vpopmail must have read access to the ~/mailman/.qmail-default file, which stores the informations about the program that will handle the delivery, Mailman in our case:

(mailman)$ chmod 644 ~/.qmail-default

Mailman will send via LMTP to our qmail MTA tons of concurrent mails  and if you enabled a limit on the number of recipients specified per message, you need to change that limit to accomodate the number of recipients of your lists. If you are using the qmail-maxrcpt patch that is built in my qmail package, check the limit in the qmail/control/maxrcpt file. You can set DISABLE_MAXRCPT in your tcprules to disable the limit for local IPs (v. 2024.06.08 upwards).

Finally we have to avoid to run simscan's filters when qmail receives messages from the Mailman's LMTP service, as it may throw it into DKIM verification errors. Modify your tcp rules accordingly adding a QMAILQUEUE="/var/qmail/bin/qmail-queue" rule to your local IPs, for example:

127.:allow,RELAYCLIENT="",DISABLE_MAXRCPT="",QMAILQUEUE="/var/qmail/bin/qmail-queue"
0.0.0.0:allow,RELAYCLIENT="",DISABLE_MAXRCPT="",QMAILQUEUE="/var/qmail/bin/qmail-queue" 
10.0.0.:allow,RELAYCLIENT="",QMAILQUEUE="/var/qmail/bin/qmail-queue" 

Then compile the tcp rules with qmailctl cdb.

Setting up MySQL

Mailman needs a relational database to provide persistence of data. If you prefer to store the data to SQLite proceed to the next step, as it is the default database. To use MySQL, install the pymysql module first:

(mailman)$ pip install pymysql

Then prepare the database and the MySQL mailman user:

CREATE USER 'mailman'@'localhost' IDENTIFIED VIA mysql_native_password USING '***';
GRANT USAGE ON *.* TO 'mailman'@'localhost' REQUIRE NONE WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0;
CREATE DATABASE IF NOT EXISTS `mailman` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON `mailman`.* TO 'mailman'@'localhost';

Of course change localhost if the MySQL server is elsewhere in your net.

Add the database settings in the ~/etc/mailman.cfg file

(mailman)$ cat >> ~/etc/mailman.cfg << __EOF__

[database] 
class: mailman.database.mysql.MySQLDatabase 
url: mysql+pymysql://mailman:password@localhost/mailman?charset=utf8mb4&use_unicode=1
__EOF__

Running Mailman

To start/stop the server you can do mailman start/stop as the mailman user. If your system has systemd please refer to the official documentation here, otherwise download the init script, make it executable and load it into your rc.local.

This has to be done as root

wget -O /usr/local/bin/mmctl https://notes.sagredo.eu/files/qmail/mailman/mmctl
chmod +x /usr/local/bin/mmctl
mmctl start

The startup script needs to change to the ~mailman dir due to a bug.

If the mmctl info command returns no error Mailman core is configured successfully:

# mmctl info 
GNU Mailman 3.3.9 (Tom Sawyer) 
Python 3.9.19 (main, Mar 20 2024, 14:44:21)  
[GCC 11.2.0] 
config file: /usr/local/mailman/var/etc/mailman.cfg 
db url: mysql+pymysql://mailman:password@localhost/mailman?charset=utf8mb4&use_unicode=1 
devmode: DISABLED 
REST root url: http://localhost:8001/3.1/ 
REST credentials: restadmin:restpass

You can do the same test as the mailman user in activated mode calling mailman info.

You can interact with Mailman via command line by typing mailman shell (help() for interactive help), but it will be easier to play with the Postorius web interface.

Cronjobs

Mailman requires the following cronjobs for periodic actions. Add the following to the mailman user cronjob (su - mailman && crontab -e):

@daily ID=mailman  source /usr/local/mailman/bin/activate && /usr/local/mailman/bin/mailman -C /usr/local/mailman/etc/mailman.cfg notify >> /usr/local/mailman/var/logs/cron.log 2>&1
@daily ID=mailman  source /usr/local/mailman/bin/activate && /usr/local/mailman/bin/mailman -C /usr/local/mailman/etc/mailman.cfg digests --periodic >> /usr/local/mailman/var/logs/cron.log 2>&1

Set up Mailman Web UI

Postorius and Hyperkitty are Mailman’s official Web UI and Archiver. Mailman-web provides a convenient single package to install both of these. You can run Mailman 3 without it, but most people prefer to use the web interface for changing list settings, viewing information about available lists, and subscribing or unsubscribing. Django is the python web framework used to build Postorius and Hyperkitty.

Let's enter the virtual environment again and install Mailman web:

su - mailman
source bin/activate
(mailman)$ pip install mysqlclient mailman-web mailman-hyperkitty pylibmc

We'll use the MySQL database in a new mailman_web database (the owner will be mailman, who already owns the mailman database that we created earlier):

CREATE DATABASE `mailman_web` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON `mailman_web`.* TO 'mailman'@'localhost';

The CHARACTER SET utf8mb4 parameter is important.

The ~/settings.py file will hold the settings for Mailman web. Download and adjust it to your needs:

(mailman)$ wget -O ~/etc/settings.py https://notes.sagredo.eu/files/qmail/mailman/settings.py
(mailman)$ chmod o-r ~/etc/settings.py

It's important to add your domain to the variables ALLOWED_HOSTS and CSRF_TRUSTED_ORIGINS. Here lists.mydomain.tld is the web server's virtual host used for the Mailman web site; I'm supposing that it could be the same as the domain where we decided to define the lists, but it can be a different one.

#: See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts 
ALLOWED_HOSTS = [ 
   "localhost",  # Archiving API from Mailman, keep it. 
   "127.0.0.1", 
   "0.0.0.0", 
   "lists.mydomain.tld", 
   # Add here all production domains you have. 
]

#: See https://docs.djangoproject.com/en/dev/ref/settings/#csrf-trusted-origins 
#: For Django <4.0 these are of the form 'lists.example.com' or 
#: '.example.com' to include subdomains and for Django >=4.0 they include 
#: the scheme as in 'https://lists.example.com' or 'https://*.example.com'. 
CSRF_TRUSTED_ORIGINS = [ 
   "http://lists.mydomain.tld",
   "https://lists.mydomain.tld", 
   # "lists.your-domain.org", 
   # Add here all production domains you have. 
]

Add its location to the mailman's user .profile (default would be /etc/settings.py otherwise):

(mailman)$ cat >> ~/.profile << __EOF__
export DJANGO_SETTINGS_MODULE=~/etc/settings 
__EOF__

Be carefull when setting SITE_ID. It is the ID of the Django Site being served, i.e. the site that will serve your Mailman web control panel.

#: Current Django Site being served. This is used to customize the web host 
#: being used to serve the current website. For more details about Django 
#: site, see: https://docs.djangoproject.com/en/dev/ref/contrib/sites/ 
SITE_ID = 2

When you open the Mailman web interface, you'll have an example.com site already created with SITE_ID = 1 that you may want to delete and replace with mydomain.tld, which will have SITE_ID = 2 or whatelse. Enter its value in settings.py file, presumibly it will be 2.

Create the directory where the web interface will live:

mkdir -p ~/web/logs

In the following commands we have to pass some variables to mailman-web. I don't know why it doesn't read those variables from env. Setup the database schema for Mailman’s web components:

(mailman)$ PYTHONPATH=~/etc/ DJANGO_SETTINGS_MODULE=settings mailman-web migrate

Copy all the static files (css, js, images) into the STATIC_ROOT:

(mailman)$ PYTHONPATH=~/etc/ DJANGO_SETTINGS_MODULE=settings mailman-web collectstatic

Compress the various CSS files offline (the sass package is needed):

(mailman)$ PYTHONPATH=~/etc/ DJANGO_SETTINGS_MODULE=settings mailman-web compress

Update the message catalogs for supported languages:

(mailman)$ PYTHONPATH=~/etc/ DJANGO_SETTINGS_MODULE=settings mailman-web compilemessages

Now set up the admin account. The superuser is assumed to be the Site Owner and has access to all the domains, mailing lists and their settings. You'll be asked to enter and confirm the email address of the admin account. Do not use an account such as postmaster@lists.mydomain.tld because it would be difficult to retrieve the confirmation message. Use something like postmaster@mydomain.tld instead.

(mailman)$ PYTHONPATH=~/etc/ DJANGO_SETTINGS_MODULE=settings mailman-web createsuperuser

memcached setup

Installing memcached and the pylibmc package that we have installed earlier is recommended to fix an issue concerning the erase of items in the archive (more info here), which can be solved installing a caching system that is common across all workers. memcached installation can be easily done with a package of the OS and is not covered here; just be sure that it is launched at boot time and that it is listening on the 11211 port. memcached settings are already stored in the settings.py file that we downloaded earlier:

# Using the cache infrastructure can significantly improve performance on a 
# production setup. This is an example with a local Memcached server. 
# pip install pylibmc 
CACHES = { 
   'default': { 
       'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 
       'LOCATION': '127.0.0.1:11211', 
   } 
}

Setting up a WSGI server

WSGI gets Django to work behind apache or another web server. Django is the python web framework used to build Postorius and Hyperkitty.

(mailman)$ pip install uwsgi

Download the configuration file (there's no need to modify it):

wget -O ~/etc/uwsgi.ini https://notes.sagredo.eu/files/qmail/mailman/uwsgi.ini

Test (exit with ^C):

(mailman)$ uwsgi --ini ~/etc/uwsgi.ini

Startup script

Those under systemd should refer to the official documentation here. Users who can do what they want with their system can use this scriptlet:

wget -O /usr/local/bin/uwsgictl https://notes.sagredo.eu/files/qmail/mailman/uwsgi.ini
chmod +x /usr/local/bin/uwsgictl

Add it to your rc.local or init.d. Test as root:

# uwsgictl  
Usage: /usr/local/bin/uwsgictl {start|stop|restart|reload|force-reload} 
# uwsgictl start 
Starting uWSGI Server[uWSGI] getting INI configuration from /usr/local/mailman/etc/uwsgi.ini

The log files for both UWSGI and Mailman Web are under ~/web/logs.

Setting up Fulltext search (Xapian)

To install both xapian-core and xapian-bindings in the virtual environment check the latest version number in the source site and pass it as an argument to the install script:

(mailman)$ wget https://raw.githubusercontent.com/notanumber/xapian-haystack/master/install_xapian.sh
(mailman)$ sh install_xapian.sh 1.4.25

Install Xapian-Haystack:

pip install xapian-haystack

Rebuild the index (force the rebuild):

(mailman)$ PYTHONPATH=~/etc/ DJANGO_SETTINGS_MODULE=settings mailman-web rebuild_index
WARNING: This will irreparably remove EVERYTHING from your search index in connection 'default'. 
Your choices after this are to restore from backups or rebuild via the `rebuild_index` command. 
Are you sure you wish to continue? [y/N] Y 
Removing all documents from your index because you said so. 
All documents removed. 
Indexing 11 emails

The following are already set in your ~/etc/settings.py file that you downloaded earlier:

# xapian (full text search) 
HAYSTACK_CONNECTIONS = { 
   'default': { 
       'HAYSTACK_XAPIAN_LANGUAGE': 'en', 
       'ENGINE': 'xapian_backend.XapianEngine', 
       'PATH': "/usr/local/mailman/web/xapian_index" 
   }, 
}

You can change the english default language modyfing the HAYSTACK_XAPIAN_LANGUAGE variable. The list of available languages can be found here.

Then restart the server:

uwsgictl restart

Cronjob setup for Mailman Web

As the mailman user edit the crontab (crontab -e) and install the following cronjobs:

@hourly            ID=mailman  PYTHONPATH=/usr/local/mailman/etc/ DJANGO_SETTINGS_MODULE=settings /usr/local/mailman/bin/mailman-web runjobs hourly         >/dev/null 2>&1 
@daily             ID=mailman  PYTHONPATH=/usr/local/mailman/etc/ DJANGO_SETTINGS_MODULE=settings /usr/local/mailman/bin/mailman-web runjobs daily          >/dev/null 2>&1 
@weekly            ID=mailman  PYTHONPATH=/usr/local/mailman/etc/ DJANGO_SETTINGS_MODULE=settings /usr/local/mailman/bin/mailman-web runjobs weekly         >/dev/null 2>&1 
@monthly           ID=mailman  PYTHONPATH=/usr/local/mailman/etc/ DJANGO_SETTINGS_MODULE=settings /usr/local/mailman/bin/mailman-web runjobs monthly        >/dev/null 2>&1 
@yearly            ID=mailman  PYTHONPATH=/usr/local/mailman/etc/ DJANGO_SETTINGS_MODULE=settings /usr/local/mailman/bin/mailman-web runjobs yearly         >/dev/null 2>&1 
* * * * *          ID=mailman  PYTHONPATH=/usr/local/mailman/etc/ DJANGO_SETTINGS_MODULE=settings /usr/local/mailman/bin/mailman-web runjobs minutely       >/dev/null 2>&1 
2,17,32,47 * * * * ID=mailman  PYTHONPATH=/usr/local/mailman/etc/ DJANGO_SETTINGS_MODULE=settings /usr/local/mailman/bin/mailman-web runjobs quarter_hourly >/dev/null 2>&1

Test:

(mailman)$ PYTHONPATH=~/etc/ DJANGO_SETTINGS_MODULE=settings mailman-web runjobs -l             
Job List: 11 jobs 
appname           - jobname                - when    - help 
-------------------------------------------------------------------------------- 
django_extensions - cache_cleanup          - daily   - Cache (db) cleanup Job 
django_extensions - daily_cleanup          - daily   - Django Daily Cleanup Job 
hyperkitty        - empty_threads          - monthly - Remove empty threads 
hyperkitty        - new_lists_from_mailman - hourly  - Import new lists from Mailman 
hyperkitty        - orphan_emails          - daily   - Reattach orphan emails 
hyperkitty        - recent_threads_cache   - daily   - Refresh the recent threads cache 
hyperkitty        - sync_mailman           - daily   - Sync user and list properties with Mailman 
hyperkitty        - thread_order_depth     - yearly  - Compute thread order and depth for all threads 
hyperkitty        - thread_starting_email  - hourly  - Find the starting email when it is missing 
hyperkitty        - update_and_clean_index - monthly - Update the full-text index and clean old entries 
hyperkitty        - update_index           - hourly  - Update the full-text index

The HyperKitty archiver

HyperKitty is an application providing a web interface to access Mailman archives, and interact with the lists. To connect HyperKitty with Mailman, these are the lines already set in your mailman.cfg that you downloaded earlier:

# For the HyperKitty archiver. 
[archiver.hyperkitty] 
class: mailman_hyperkitty.Archiver 
enable: yes 
configuration: /usr/local/mailman/etc/mailman-hyperkitty.cfg

Now create mailman-hyperkitty.cfg (downolad)

(mailman)$ cat > ~/etc/mailman-hyperkitty.cfg << __EOF__
# This is the mailman extension configuration file to enable HyperKitty as an 
# archiver. Remember to add the following lines in the mailman.cfg file: 
# 
# [archiver.hyperkitty] 
# class: mailman_hyperkitty.Archiver 
# enable: yes 
# configuration: /path/to/here/mailman-hyperkitty.cfg 
# 

[general] 
# This is your HyperKitty installation, preferably on the localhost. This 
# address will be used by Mailman to forward incoming emails to HyperKitty 
# for archiving. It does not need to be publicly available, in fact it's 
# better if it is not. 
# However, if your Mailman installation is accessed via HTTPS, the URL needs 
# to match your SSL certificate (e.g. https://lists.example.com/hyperkitty). 
base_url: http://lists.mydomain.tld/archives/ 

# The shared api_key, must be identical except for quoting to the value of 
# MAILMAN_ARCHIVER_KEY in HyperKitty's settings. 
api_key: xxxxxxxxxxxxxxxxxxx
__EOF__

It is important that the api_key value matches the MAILMAN_ARCHIVER_KEY value inside settings.py.

Apache configuration

Here is an example of apache virtual host. mod_proxy must be enabled:

Define MMDIR /usr/local/mailman 
Define MMDOMAIN lists.mydomain.tld

<VirtualHost *:443> 
 ServerName  ${MMDOMAIN}
 # Include SSL stuff

 CustomLog ${LOGDIR}/${MMDOMAIN}.log combined 
 ErrorLog  ${LOGDIR}/${MMDOMAIN}_error.log 

 Alias /static "${MMDIR}/web/static" 
 Alias /favicon.ico $MMDIR/web/static/hyperkitty/img/favicon.ico 
 <Directory "${MMDIR}/web/static"> 
   Require all granted 
 </Directory> 

 <IfModule mod_rewrite.c> 
   RewriteEngine On 
   # redirects / calls to /mailman3 
   RewriteRule ^/$ https://${MMDOMAIN}/mailman3 [R=302,L] 
 </IfModule> 

 <IfModule mod_proxy.c> 
   SSLProxyEngine On 
   ProxyRequests Off 
   ProxyPreserveHost On 
   ProxyPass "/mailman3"     "http://127.0.0.1:8000/mailman3" 
   ProxyPass "/archives"     "http://127.0.0.1:8000/archives" 
   ProxyPass "/accounts"     "http://127.0.0.1:8000/accounts" 
   ProxyPass "/admin"        "http://127.0.0.1:8000/admin" 
   ProxyPass "/user-profile" "http://127.0.0.1:8000/user-profile" 
   ProxyPassMatch ^/static/ ! 
 </IfModule> 
</VirtualHost>

Once finished browse to https://lists.mydomain.org/mailman3 and login with the superuser credentials. Then confirm the superuser's email address and login again.

logrotate

Here is a logrotate file to rotate both Mailman and Mailman web files:

cat > /etc/logrotate.d/mailman << __EOF__
#mailman 
/usr/local/mailman/var/logs/*.log { 
  missingok 
  sharedscripts 
  su mailman mailman 
  postrotate 
    # /usr/local/bin/mmctl reopen &>/dev/null || true # users with no systemd
    cd /usr/local/mailman 
    sudo -u mailman /usr/local/mailman/bin/mailman -C /usr/local/mailman/etc/mailman.cfg reopen &>/dev/null || true
  endscript 
} 

# mailman-web 
/usr/local/mailman/web/logs/*.log { 
  hourly 
  missingok 
  notifempty 
  delaycompress 
  su mailman mailman 
}
__EOF__

Adding domains

With the previous instructions all the lists belong to the same domain (lists.mydomain.tld in our example). What do you need to do if you want to create lists on other domains, say otherdomain.tld?

Create a Linux user and link the virtualdomain otherdomain.tld to that user:

useradd -d /home/username username
mkdir -p /home/username
chown -R username:users /home/username
echo otherdomain.tld:username >> /var/qmail/control/virtualdomains

In this way qmail will seek the dot-qmail file which is responsible to handle the delivery in the user's home directory. Therefore we have to save there the instructions to call the Mailman's LMTP server:

echo "|/usr/local/mailman/qmail-lmtp 8024 1" > /home/username/.qmail-default
chown username:users /home/username/.qmail-default

You may think that the same result could be achieved simply creating an alias /var/qmail/alias/.qmail-username-default but this method woudn't work, because qmail would pass Mailman the recipient address username-listname@otherdomain.tld instead of listname@otherdomain.tld and you'll get a "mailbox not found" reject.

As you know Django comes with a “sites” framework. It’s a hook for associating objects and functionality to particular websites, and it’s a holding place for the domain names and “verbose” names of your Django-powered sites.You should have already created the lists.mydomain.tld site with SITE_ID=2 or whatever as SITE_ID. When you create your lists belonging to the newly created domain in the Postorius web panel, you'll have to choose lists.mydomain.tld as its "Web server".

Adding mailing lists to your main domain

You may be wondering if you can define a list like list@mydomain.tld, where mydomain.tld is a virtual domain with ordinary qmail/vpopmail users and with its own .qmail-default file. The answer is yes. Just create the dot-qmail file with the name that you want to assign to the list, call Mailman in the usual way and you're done:

echo "|/usr/local/mailman/qmail-lmtp 8024 1" > ~vpopmail/domains/mydomain.tld/.qmail-list

Known issues

  • Mailman web crashes with "AttributeError: 'NoneType' object has no attribute 'thread'" as soon as I click on the "Reattach this conversation" button in the HyperKitty archive. I opened a ticket here.
  • As already mentioned, the original qmail-lmpt script doesn't work with Mailman3 because qmail-local sends bare LF for the end line, while the SMTP standards require CRLF, so the SMTP conversation between qmail and Mailman ends up with the error: "Line too long (see RFC5321 4.5.3.1.6)". The patched qmail-lmtp file that we are using here can have unpredictable results when binary files with \n and \r characters are sent, according to what a developer says in this conversation. Anyhow it works fine here in my server so far.

List owner instructions

If your users ask you to provide a manual for the list administrator you can suggest this video guide, which covers everything.

The manual for the list member is in the official guide here.

Add a comment

Recent comments
Recent posts

RSS feeds