Graph and PowerShell Blog | About | Links
Adding a Postfix server for relay to O365
08-Jun-24

Postfix Articles
  • Adding a Postfix server for relay to O365
  • Advanced Postfix configuration
  • Postfix routing by filtering
  • Postfix Receive and Forward

  • After coming to understand that apps that offer a username and password doesn't mean they necessarily support client submission on port 587, I needed another way to move apps away from Exchange 2016 on-prem for mail relay to Office365.

    The most common choice is to set up an Ubuntu server with Postfix and then have Postfix submit the email with an O365 account. However, that has the major drawback of only allowing one email 'from address' to be used. I have many apps to support and some that are important enough to require their own address.

    A better option is to use Postfix and connect to an O365 receive connector (aka Inbound Connector), which allows for server-to-server communication over port 25. The recommended way to secure the connection is with a 3rd party cert, but for testing, you can use an external IP address.

    Having limited Linux experience over the years I was a little surprised at how bad certain things were:
  • Keyboard support would always default back to US after a reboot no matter how many settings I had.
  • The RSyslog defaulted to US date format, when I tried to install RSyslog I got an error that I couldn't recover, so now I'm stuck with that in the Postfix logs.
  • Not strictly a Linux issue but currently Microsoft does not support PowerShell on Ubuntu 24.04. Hopefully, this will be fixed soon.

  • Despite that, there were at least workarounds for the above problems. Postfix is lightweight and easy to configure while Linux is stable and reboots in under a minute if maintenance is ever required. This is useful if you only plan to run one server and not load balance.

    The config is simple enough, you want to set the smart host so it points to O365 for all received mail:

    /etc/postfix/main.cf
    ===================
    relayhost = [domain-com.mail.protection.outlook.com]:25

    What apps can relay via the server can be restricted by IP, this is important because the connector to O365 is an open relay, with only some filtering in place.

    mynetworks = 192.168.1.2

    We need the Postfix server to relay to O365 using encryption, so having purchased and installed a 3rd party cert we set the following config:

    smtp_tls_security_level = encrypt
    smtp_use_tls = yes
    smtp_tls_note_starttls_offer=yes
    smtp_tls_key_file = /etc/ssl/private.key
    smtp_tls_cert_file = /etc/ssl/relay_cert.cer
    smtp_tls_CAfile = /etc/ssl/inter_cert.cer

    Next, we restarted the Postfix service and set the O365 receive connector to validate by matching the cert to the domain name.

    ↑ Connector validates against the cert name, this is the recommended setup.

    With that in-pace I sent a test mail, not expecting it to work the first time, but there was a nice surprise.

    ↑ Email received and sent to O365 using TLS 1.3. The time delay turned out to be NTP not being setup on Ubuntu.

    One thing to note is you will probably need to update your SPF record to include the external IP address that the relay server uses. The messages that arrive via the O365 connector are still subject to spam filtering.

    The next test was to send to an external recipient, eg relay@domain.com to joe.soap@gmail.com. This worked without issue and the email was authenticated in Gmail, so no spam issues to deal with there. O365 will add its own DKIM mark to the outgoing mail.

    At this point, all of the functionality that was in my old Exchange server was now present in the Postfix server. But what if you want to provide a better service? Most apps will relay in clear text on port 25 to the relay server but some support implicit SSL on port 465. To support this we add the following to the main.cf file:

    smtpd_tls_key_file = /etc/ssl/private.key
    smtpd_tls_cert_file = /etc/ssl/relay_cert.cer
    smtpd_tls_CAfile = /etc/ssl/inter_cert.cer
    smtpd_use_tls=yes
    smtpd_tls_security_level=may
    smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

    I have to admit I tested this with Powershell's send-mailmessage and got an obscure cert error message.

    Send-MailMessage -To joe.soap@gmail.com -from relay@domain.com -Subject 'test' -SmtpServer server.domain.local -UseSsl -Port 465
    Send-MailMessage : The remote certificate is invalid according to the validation procedure.

    I eventually worked out that with PowerShell the specified SMTP server has to match the certificate name. So you may need to edit the hosts file to point relay.domain.com to your internal Postfix server. Most apps don't seem to be as fussy as PowerShell.
    I also tested with OpenSSL and was able to send a test mail, the only difference in the headers is that we see ESMTPS instead of ESMTP.


    ↑ ESMTPS means the email was encrypted from the app server to Postfix.

    Some commands that help administer Ubuntu and Postfix:
    postfix reload # restart the Postfix service
    postfix logrotate # saves off the existing postfix.log and creates a new log
    sudo reboot # reboot the server, needed after some config changes
    setxkbmap gb # set keyboard to UK, unfortunately doesn't stick after reboot

    Further Postfix config explained:
    smtp_*** # server to server communication
    smtpd_*** # client to server communication

    smtp_tls_key_file = /etc/ssl/private.key # your private key
    smtp_tls_cert_file = /etc/ssl/relay_cert.cer # your public key
    smtp_tls_CAfile = /etc/ssl/inter_cert.cer # your intermediate key, O365 does not seem to require this but it's good practice to include it

    smtp_use_tls = yes # opportunistic, use TLS when a remote SMTP server announces STARTTLS support
    smtp_tls_security_level = encrypt # enforces TLS to be used when sending mail
    smtp_tls_note_starttls_offer=yes # keeps a log of servers using TLS, not really sure if needed

    smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache # used to cache TLS requests, needed for performance

    smtpd_tls_loglevel = 1# shows what TLS version is used by clients, adds line to log

    myhostname = relay.domain.com # hostname returned to telnet EHLO

    maillog_file = /var/log/postfix.log # where to place the Postfix log file
    compatibility_level = 3.6 # backwards compatibility safety guard
    alias_maps = hash:/etc/aliases# mapping logons to addresses, only added to remove error in log

    Full config file main.cf:
    # General
    # =======
    compatibility_level = 3.6
    maillog_file = /var/log/postfix.log
    alias_maps = hash:/etc/aliases


    # Server to Server
    # ================
    smtp_tls_key_file = /etc/ssl/private.key
    smtp_tls_cert_file = /etc/ssl/relay_cert.cer
    smtp_tls_CAfile = /etc/ssl/inter_cert.cer
    smtp_use_tls = yes
    smtp_tls_security_level = encrypt
    smtp_tls_note_starttls_offer=yes
    smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

    relayhost = [domain-com.mail.protection.outlook.com]:25
    myhostname = relay.domain.com


    # Client to Server
    # ================
    smtpd_tls_key_file = /etc/ssl/private.key
    smtpd_tls_cert_file = /etc/ssl/relay_cert.cer
    smtpd_tls_CAfile = /etc/ssl/inter_cert.cer
    smtpd_use_tls=yes
    smtpd_tls_security_level=may
    smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
    smtpd_tls_loglevel = 1

    mynetworks = 192.168.1.2 192.168.1.17

    References: https://secopsmonkey.com/better-mail-relaying-postfix-through-office-365.html