memoru

How To: Install an E-Mail Server on Fedora 43

Last updated: 2026-02-01

PostgreSQL will be used to store data about mailboxes and aliases.

This guide uses Postfix v3.10.3 and Dovecot v2.4.1.

Configuration

So you can copy and paste configurations and commands if in a hurry, fill in these:

A password is generated by JavaScript on your browser on each page load

Install NGINX if not already installed (assumed for Let's Encrypt certificate retrieval and Thunderbird Autoconfig):

sudo dnf install nginx && sudo systemctl enable --now nginx

OpenDKIM (DomainKeys Identified Mail)

To sign outgoing e-mail and verify incoming e-mail.

sudo dnf install opendkim opendkim-tools
In file /etc/opendkim.conf set the domain and the mode to sign and verify:
Domain TTexample.org
Mode sv

Generate the signing key for the domain:

cd /etc/opendkim/keys
sudo opendkim-genkey -d TTexample.org
sudo chown opendkim:opendkim /etc/opendkim/keys/*

Start the OpenDKIM service and make it start with the system:

sudo systemctl enable --now opendkim

DNS

Create the following record, replacing GET_ME_FROM_DEFAULTTXT by what comes after p= in /etc/opendkim/keys/default.txt:
default._domainkey.TTexample.org. 1 IN TXT "v=DKIM1;t=s;h=sha256;k=rsa;p=GET_ME_FROM_DEFAULTTXT"

SpamAssassin

SpamAssassin gives a spam score to incoming e-mail and adds [SPAM] to the e-mail subject if it's above a certain threshold.

sudo dnf install spamassassin
sudo systemctl enable --now spamassassin
sudo useradd -d /var/spool/spamassassin -s /bin/false spamassassin

Postfix

DNS

Create those records in your zone:

smtp.TTexample.org. 1 IN CNAME server1.exampleTT.org.

TTexample.org. 1 IN MX 100 smtp.TTexample.org.

default._bimi.TTexample.org. 1 IN TXT "v=BIMI1;l=https://www.TTexample.org/images/logo.svg"

_dmarc.TTexample.org. 1 IN TXT "v=DMARC1; p=reject; rua=mailto:postmaster@TTexample.org;"

TTexample.org. 1 IN TXT "v=spf1 mx -all"

The BIMI record is used to display a brand logo in Gmail and other e-mail clients.

The v=spf1 mx -all SPF record will make receiving SMTP servers reject e-mail for the domain if it is not coming from the servers in your MX records or DKIM verification fails.

TLS Certificate

sudo certbot certonly --nginx -d smtp.TTexample.org

Configuration

In the following, "virtual mailbox" means that mailboxes do not have an associated Unix system account. Accounts are instead stored in PostgreSQL.

Install the Postfix packages:

sudo dnf install postfix postfix-pgsql

Create the user and the directories which will contain the actual email data:

sudo useradd --home-dir /var/mail --user-group --uid 5000 vmail
sudo rm /var/mail/vmail
sudo mkdir -p /var/mail/vmail/TTexample.org
sudo chown -R vmail:vmail /var/mail/vmail

If you don't yet have a PostgreSQL cluster, set it up:

sudo dnf install postgresql-server && sudo postgresql-setup --initdb && sudo systemctl enable --now postgresql

Set up the database (make sure to change the postmaster mailbox password):

sudo -u postgres psql
CREATE DATABASE vmail;

\c vmail

CREATE TABLE addresses (
    email TEXT NOT NULL PRIMARY KEY,
    active BOOLEAN NOT NULL DEFAULT true,
    passwd TEXT NOT NULL
);

CREATE TABLE aliases (
    source TEXT NOT NULL PRIMARY KEY,
    target TEXT NOT NULL
);

CREATE USER vmail WITH ENCRYPTED PASSWORD 'TTpostgres-password';
GRANT SELECT ON public.addresses TO vmail;
GRANT SELECT ON public.aliases TO vmail;

Ensure configuration in /var/lib/pgsql/data/pg_hba.conf is such that Postfix and Dovecot can connect through the Unix domain socket with the password authentication method.

Create file /etc/postfix/pgsql_mailboxes.cf:
user = vmail
password = TTpostgres-password
hosts = /run/postgresql
dbname = vmail
query = SELECT 1 FROM addresses WHERE email = '%s'
Create file /etc/postfix/pgsql_aliases.cf`:
user = vmail
password = TTpostgres-password
hosts = /run/postgresql
dbname = vmail
query = SELECT 1 FROM aliases WHERE source = '%s'
    
Edit file /etc/postfix/main.cf and merge configuration:
myhostname = server1.exampleTT.org
mydomain = TTexample.org
mydestination = localhost.$mydomain, localhost
inet_interfaces = all
smtpd_tls_cert_file = /etc/letsencrypt/live/smtp.TTexample.org/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/smtp.TTexample.org/privkey.pem
virtual_mailbox_base = /var/mail/vmail
virtual_mailbox_domains = TTexample.org
virtual_mailbox_maps = pgsql:/etc/postfix/pgsql_mailboxes.cf
virtual_alias_maps = pgsql:/etc/postfix/pgsql_aliases.cf
virtual_gid_maps = static:5000
virtual_uid_maps = static:5000
virtual_minimum_uid = 5000
virtual_transport = lmtp:unix:private/dovecot-lmtp
smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
milter_default_action = accept
milter_protocol = 2
smtpd_milters = unix:/run/opendkim/opendkim.sock
non_smtpd_milters = unix:/run/opendkim/opendkim.sock

In file /etc/postfix/master.cf, uncomment smtp, submission and submissions (the services without 127.0.0.1, to listen on all interfaces!) and their arguments (beware not to uncomment actual comments about the configuration!).

Then, add as an argument to service smtp:

  -o content_filter=spamassassin

Then, add a new service:

spamassassin unix -     n       n       -       -       pipe user=spamassassin argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}

Start the Postfix systemd service and make it start with the system:

systemctl enable --now postfix

Dovecot

DNS

imap.TTexample.org. 1 IN CNAME server1.exampleTT.org.

TLS Certificate

sudo certbot certonly --nginx -d imap.TTexample.org

Configuration

sudo dnf install dovecot dovecot-pgsql dovecot-pigeonhole
openssl dhparam -out /etc/dovecot/dh.pem 4096 Edit file /etc/dovecot/dovecot.conf:
ssl_server {
  cert_file = /etc/letsencrypt/live/imap.TTexample.org/fullchain.pem
  key_file = /etc/letsencrypt/live/imap.TTexample.org/privkey.pem
  dh_file = /etc/dovecot/dh.pem
}

mail_location = maildir:/var/mail/vmail/%{user | domain}/%{user | username}
mail_uid = 5000
mail_gid = 5000
mail_privileged_group = vmail

pgsql /run/postgresql {
	parameters {
		host=/run/postgresql
		user=vmail
		password=TTpostgres-password
		dbname=vmail
	}
}

passdb sql {
  default_password_scheme = ARGON2ID

  query = \
	SELECT email as username, password FROM addresses WHERE email = '%{user}'
}

userdb sql {
  query = \
    SELECT 5000 AS uid, 5000 as gid, '/var/mail/vmail/%{user | domain}/%{user | username}' AS home \
    FROM addresses WHERE email = '%{user}'

  iterate_query = SELECT email AS user FROM addresses
}
Create file /etc/dovecot/conf.d/20-lmtp.conf:
protocol lmtp {
    # Space separated list of plugins to load (default is global mail_plugins).
    mail_plugins = %{mail_plugins} sieve
    postmaster_address = postmaster@TTexample.org
}
Edit file /etc/dovecot/conf.d/10-master.conf:
service imap-login {
    inet_listener imap {
        port = 143
    }
    inet_listener imaps {
        port = 993
        ssl = yes
    }
}

service lmtp {
    unix_listener /var/spool/postfix/private/dovecot-lmtp {
        mode = 0600
        user = postfix
        group = postfix
    }
}

service auth {
    #unix_listener auth-userdb {
    #  #mode = 0666
    #  #user =
    #  #group =
    #}

    # Postfix smtp-auth
    unix_listener /var/spool/postfix/private/auth {
        mode = 0666
    }
}

Start the dovecot systemd service and make it start with the system:

systemctl enable --now dovecot

Setting up Thunderbird Autoconfig

DNS

Create the following record in your DNS zone:

autoconfig.TTexample.org.	1	IN	CNAME	server1.exampleTT.org.

Issue a new Let's Encrypt TLS certificate:

sudo certbot certonly --nginx -d autoconfig.TTexample.org

Create file: /etc/nginx/conf.d/autoconfig.TTexample.org.conf with the following contents:

server {
    listen                  443 ssl http2;
    listen                  [::]:443 ssl http2;
    server_name             autoconfig.TTexample.org;

    # SSL
    ssl_certificate         /etc/letsencrypt/live/autoconfig.TTexample.org/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/autoconfig.TTexample.org/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/autoconfig.TTexample.org/chain.pem;

    # logging
    access_log              /var/log/nginx/access.log combined buffer=512k flush=1m;
    error_log               /var/log/nginx/error.log warn;

    root /var/www/autoconfig.TTexample.org;

    include sts;
}

# HTTP redirect
server {
    listen      80;
    listen      [::]:80;
    server_name autoconfig.TTexample.org;

    location / {
        return 301 https://autoconfig.TTexample.org/$request_uri;
    }
}

Create file: /var/www/autoconfig.TTexample.org/mail/config-v1.1.xml:

<?xml version="1.0" encoding="UTF-8"?>
<clientConfig version="1.1">
    <emailProvider id="TTexample.org">
    <domain>TTexample.org</domain>
    <displayName>TTexample.org mail service</displayName>
    <displayShortName>TTexample.org mail service</displayShortName>
    <incomingServer type="imap">
        <hostname>imap.TTexample.org</hostname>
        <port>993</port>
        <socketType>SSL</socketType>
        <authentication>password-cleartext</authentication>
        <username>%EMAILADDRESS%</username>
    </incomingServer>
    <outgoingServer type="smtp">
        <hostname>smtp.TTexample.org</hostname>
        <port>465</port>
        <socketType>SSL</socketType>
        <authentication>password-cleartext</authentication>
        <username>%EMAILADDRESS%</username>
    </outgoingServer>
    </emailProvider>
</clientConfig>

Reload NGINX's configuration with: sudo systemctl reload nginx

Creating mailboxes

Obtain a password hash using doveadm pw -s ARGON2ID then insert the new address in the database:

sudo -u postgres psql vmail
INSERT INTO public.addresses (email, active, passwd) VALUES ('postmaster@TTexample.org', true, 'YOURHASHHERE');