I needed to host some mailing lists, so I started setting up mailman3 on an Archlinux system. It was quite bumpy ride as the documentation in the Arch Wiki lacked some steps and other documentation was not made for Arch or completely outdated.
mailman3 is a rewrite of the mailman2 suite. The biggest differences is that the webui is now a second project, postorius, and needs to be setup independently. There is also an archiver, hyperkitty, which I don’t need and will not be part of this document.
First we need to install the necessary software:
pacman -S postfix mailman3 postorius nginx gunicorn certbot-nginx
We need nginx for the postorius and gunicorn makes it possible to run postorius, which uses Django, as a systemd process. certbot-nginx is needed for the letsencrypt certificates.
Now we will enable and start the processes:
systemctl enable mailman3.service
systemctl enable mailman3-digests.timer
systemctl enable mailman3-gatenews.timer
systemctl enable mailman3-notify.timer
systemctl enable nginx
We need to create some files and set them up with the proper rights:
touch /var/log/gunicorn-error.log
chown postorius:postorius /var/log/gunicorn-error.log
touch /var/log/gunicorn-access.log
chown postorius:postorius /var/log/gunicorn-access.log
mkdir /run/gunicorn
chown -R postorius:postorius /run/gunicorn
touch /srv/http/DOMAINNAME
chown -R http:http /srv/http/
After first installation of postorius make sure to generate a database as the user postorius (user has shell /usr/bin/nologin in /etc/passwd):
[postorius]$ django-admin migrate --pythonpath /usr/share/webapps/postorius/ --settings settings
Afterwards, the static data for the application needs to be collected:
[postorius]$ django-admin collectstatic --pythonpath /usr/share/webapps/postorius/ --settings settings
Create a superuser account (listadmin) for the Django application. Give the admin user a working mailadress as it will be verified:
[postorius]$ django-admin createsuperuser --pythonpath /usr/share/webapps/postorius/ --settings settings
Nginx config for the proxy to postorius:
user http;
worker_processes auto;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
server {
server_name DOMAINNAME www.DOMAINNAME mail.DOMAINNAME;
server_name_in_redirect off;
root /srv/http/DOMAINNAME/www/;
access_log /var/log/nginx/DOMAINNAME-access.log;
error_log /var/log/nginx/DOMAINNAME-error.log;
index index.html index.php index.htm;
location ~* ^.+\.(ico|js|gif|jpg|jpeg|png|bmp)$ {
expires 30d;
}
location ~ /\.ht {
deny all;
}
}
server {
server_name lists.DOMAINNAME;
access_log /var/log/nginx/lists.DOMAINNAME-access-postorius.log;
error_log /var/log/nginx/lists.DOMAINNAME-error-postorius.log;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:8000;
}
location /static/ {
alias /var/lib/postorius/static/;
}
}
}
/etc/mailman.cfg
[mailman]
layout: fhs
site_owner: postmaster@DOMAINNAME
default_language: de
[webservice] # REST Service von Mailman. Wird von Web Frontend verwendet
port: 8001
admin_user: rest_admin_user
admin_pass: rest_admin_password
[database]
url: sqlite:////var/lib/mailman/data/mailman.db
[mta]
incoming: mailman.mta.postfix.LMTP
outgoing: mailman.mta.deliver.deliver
lmtp_host: mail.DOMAINNAME
lmtp_port: 8024
smtp_host: mail.DOMAINNAME
smtp_port: 25
#smtp_secure_mode: starttls
smtp_secure_mode: smtp
postfix main.conf:
myhostname = MAILSERVER-DOMAIN
mydestination = $myhostname, localhost, 127.0.0.1, DOMAINNAME
mynetworks = localhost, PUBLICIP, 127.0.0.0/8
inet_protocols = ipv4
inet_interfaces = all
alias_maps = hash:/etc/aliases
smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service inet:127.0.0.1:10030
compatibility_level=3.6
recipient_delimiter = +
unknown_local_recipient_reject_code = 550
owner_request_special = no
transport_maps = hash:/var/lib/mailman/data/postfix_lmtp
local_recipient_maps = hash:/var/lib/mailman/data/postfix_lmtp
relay_domains = hash:/var/lib/mailman/data/postfix_domains
debug_peer_level = 15
smtpd_sasl_auth_enable = yes
smtpd_sasl_authenticated_header = yes
smtpd_tls_auth_only = no
smtp_use_tls = yes
smtpd_use_tls = yes
smtp_tls_note_starttls_offer = yes
smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
smtpd_tls_key_file = /etc/letsencrypt/live/CERTNAME/privkey.pem
smtpd_tls_cert_file = /etc/letsencrypt/live/CERTNAME/fullchain.pem
virtual_maps = hash:/etc/postfix/virtual
Enable and start nginx before creating the letsencrypt certificates
systemctl enable nginx
systemctl start nginx
Create letsencrypt certificate
certbot --nginx -d kollhackerektiv.de -d www.DOMAINNAME -d lists.DOMAINNAME
certbot --nginx -d mail.DOMAINNAME
Start postfix and mailman
systemctl enable postfix
systemctl start postfix
systemctl start mailman3.service
systemctl start mailman3-digests.timer
systemctl start mailman3-gatenews.timer
systemctl start mailman3-notify.timer
/etc/systemd/system/gunicorn.service
[Unit]
Description=GNU Mailman web interfaces
After=syslog.target network.target
[Service]
PIDFile=/run/gunicorn.pid
WorkingDirectory=/usr/share/webapps/postorius
ExecStart=/usr/bin/gunicorn -c /usr/share/webapps/postorius/gunicorn.conf wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
# Change to a different user (and group) here
User=postorius
Group=postorius
[Install]
WantedBy=multi-user.target
/usr/share/webapps/postorius/gunicorn.conf
#bind = ['127.0.0.1:8000']
proc_name = "postorius"
#chdir = "/opt/mailman/mm"
chdir = "/usr/share/webapps/postorius/"
pidfile = "/run/gunicorn/gunicorn.pid"
accesslog = "/var/log/gunicorn-access.log"
errorlog = "/var/log/gunicorn-error.log"
#daemon = True
workers = 4
/etc/tmpfiles.d/gunicorn.conf
D /run/gunicorn 0750 postorius postorius
settings_local.py for postorius. File is in /etc/webapps/postorius/
DEFAULT_AUTO_FIELD='django.db.models.AutoField'
SECRET_KEY = 'secret'
DEBUG = True
MAILMAN_REST_API_URL = 'http://localhost:8001'
MAILMAN_REST_API_USER = 'username'
MAILMAN_REST_API_PASS = 'password'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'localhost'
EMAIL_PORT = 25
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
ALLOWED_HOSTS = [
'localhost',
'127.0.0.1',
'PUBLIC_IP',
'DOMAINNAME'
]
EMAIL_CONFIRMATION_FROM = 'postmaster@DOMAINNAME'
POSTORIUS_TEMPLATE_BASE_URL = 'https://lists.DOMAINNAME'
CSRF_TRUSTED_ORIGINS = [
'localhost',
'127.0.0.1',
'PUBLIC_IP',
'DOMAINNAME'
]
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Berlin'
USE_TZ = True
DEFAULT_FROM_EMAIL = 'postmaster@DOMAINNAME'
Start gunicorn:
systemctl enable gunicorn.service
systemctl start gunicorn.service
Hints 🔗
-
don’t use masquerade_domains in main.conf as it will masquerade the subdomain used for the list server.
-
Mailman commands should always be made as the mailman user, otherwise the permissions are fucked up
-
Switch every mailing list from digest to non digest mode when creating with postorius
Addons 🔗
- Use DMARC, DKIM and SPF
- Integrate dovecot for user accounts with IMAP and SMTP