Proxmox - Email System

So, you want to build an internal email system that will send you emails from your server and containers to an external email address.
Well having done lots of trial and error, this system works and only requires a smtp server to receive the emails and forward them to the respective email address. It relies on the postfix service that already exists on most Debian systems.
To start we will setup the Proxmox server.
#!/bin/bash
# Updated 01-02-2025
# Refer to the following along with fail2ban
# https://superuser.com/questions/576751/example-of-fail2ban-configuration-to-ban-servers-spamming-my-postfix-server
# Variables
MAIN_CF="/etc/postfix/main.cf"
SASL_PASSWD="/etc/postfix/sasl_passwd"
MAILGUN_SMTP="[external smtp mail server]"
MAILGUN_PORT="587"
MAILGUN_USER="[external email address]"
MAILGUN_PASS="[external email password"
TIMESTAMP=$(date +%Y%m%d%H%M%S)
EMAIL_ADDRESS="[external testing address]"
# Install necessary SASL modules
apt-get install -y libsasl2-modules
#Make the mail log
touch /var/log/mail.log
# Backup existing main.cf file with timestamp
cp $MAIN_CF "${MAIN_CF}-${TIMESTAMP}.bak"
# Update main.cf file
cat <<EOF > $MAIN_CF
# See /usr/share/postfix/main.cf.dist for a commented, more complete version
# compatibility level - refer to base file
compatibility_level=3.6
myhostname=[fqdn proxmox server]
smtpd_banner = \$myhostname ESMTP \$mail_name (Debian/GNU)
biff = no
# Listen on all interfaces or the pxe wont relay from LXC
inet_interfaces = all
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Setup mail logging
maillog_file = /var/log/mail.log
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydestination = \$myhostname, localhost.\$mydomain, localhost
relayhost = [$MAILGUN_SMTP]:$MAILGUN_PORT
mynetworks = 127.0.0.0/8, [ip addresses in cidr ip4 ip6]
recipient_delimiter = +
# Mailgun SMTP settings
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:$SASL_PASSWD
smtp_sasl_security_options = noanonymous
smtp_tls_security_level = encrypt
smtp_tls_note_starttls_offer = yes
# Enforce policies
smtpd_recipient_restrictions =
permit_mynetworks,
reject_unauth_destination,
reject_unknown_reverse_client_hostname,
reject_non_fqdn_sender,
reject_non_fqdn_recipient,
reject_unknown_sender_domain,
reject_unknown_recipient_domain,
permit_auth_destination,
permit
# Additional restrictions
smtpd_sender_restrictions =
reject_unknown_sender_domain,
reject_unauth_pipelining
EOF
# Create sasl_passwd file with Mailgun credentials
echo "[$MAILGUN_SMTP]:$MAILGUN_PORT $MAILGUN_USER:$MAILGUN_PASS" > $SASL_PASSWD
# Secure sasl_passwd file
chmod 600 $SASL_PASSWD
# Create hash database for sasl_passwd
postmap $SASL_PASSWD
# Restart Postfix service
systemctl restart postfix
# Enable Postfix service to start on boot
systemctl enable postfix
# Run postfix check and ensure it returns nothing
POSTFIX_CHECK=$(postfix check)
if [ -n "$POSTFIX_CHECK" ]; then
echo "Warning: Postfix check returned the following issues:"
echo "$POSTFIX_CHECK"
else
echo "Postfix configuration is correct."
fi
echo "Postfix has been configured to use Mailgun SMTP server."
# Test email to verify setup with hostname
echo "Sending test email for verification..."
echo "This is a test email from $(hostname -f)" | mail -s "Test Email from $(hostname -f)" ${EMAIL_ADDRESS}
The script creates a backup but does not take into account any firewall you may have configured, so you will need to allow for this. I have included the fail2ban reference for postfix. Adjust your fail2ban configuration as required, which is out of scope so I will not go into it.
Now we setup the LXC hosts.
#!/bin/bash
# THIS IS A GENERIC CONFIGURATION FOR LXC CONTAINERS
# Updated 08-03-2025
# Minor changes to code - works fine
# Variables
MAIL_SERVER="[fqdn proxmox server]"
EMAIL_ADDRESS="[external email address]"
MAIN_CF="/etc/postfix/main.cf"
TIMESTAMP=$(date +%Y%m%d%H%M)
# Install Postfix and rsyslog
apt-get update
apt-get install -y postfix rsyslog mailutils sudo
# Backup existing main.cf file with timestamp
cp $MAIN_CF "${MAIN_CF}-${TIMESTAMP}.bak"
# Create /etc/mailname
echo "$(hostname -f)" > /etc/mailname
# Start from a fresh slate - remove previous entries.
cp /usr/share/postfix/main.cf.debian /etc/postfix/main.cf
sed -i '/^root:/d' /etc/aliases
sed -i '/^docker:/d' /etc/aliases
# Configure Postfix
debconf-set-selections <<< "postfix postfix/mailname string $(hostname -f)"
debconf-set-selections <<< "postfix postfix/main_mailer_type string 'Satellite system'"
debconf-set-selections <<< "postfix postfix/relayhost string ${MAIL_SERVER}"
# Modify /etc/postfix/main.cf
cat <<EOF >> /etc/postfix/main.cf
relayhost = ${MAIL_SERVER}
myhostname = $(hostname -f)
smtp_helo_name = $(hostname -f)
myorigin = /etc/mailname
mydestination = ${EMAIL_ADDRESS}
# Enable logging
maillog_file = /var/log/mail.log
EOF
# Add root alias to /etc/aliases and docker if on a docker host
echo "root: ${EMAIL_ADDRESS}" >> /etc/aliases
echo "docker: ${EMAIL_ADDRESS}" >> /etc/aliases
newaliases
# Create the file if it doesnt exist - this is the default location
touch /var/log/mail.log
# Set proper permissions for /var/log/mail.log
chown root:sudo /var/log/mail.log
chmod 640 /var/log/mail.log
# Restart rsyslog and Postfix
systemctl enable postfix
systemctl restart rsyslog
systemctl restart postfix
# Run postfix check and ensure it returns nothing
POSTFIX_CHECK=$(postfix check)
if [ -n "$POSTFIX_CHECK" ]; then
echo "Warning: Postfix check returned the following issues:"
echo "$POSTFIX_CHECK"
else
echo "Postfix configuration is correct."
fi
# Test email to verify setup with hostname
echo "Sending test email for verification..."
echo "This is a test email from $(hostname -f)" | mail -s "Test Email from $(hostname -f)" ${EMAIL_ADDRESS}
Once again, script has a backup function but does not allow for the firewall or fail2ban for that matter so you will need to adjust it if required.
This should allow all containers to send emails to the to the proxmox relay for onforwarding to the external email address.
It's very simple and there are other methods.
#enoughsaid