Setting up Postfix with external SMTP

Having trouble getting mail to send with Postfix? Learn how to configure Postfix with an external SMTP server.

Setting up Postfix with external SMTP
Photo by Glenn Carstens-Peters / Unsplash

Getting a new server up and running isn't quite complete until you can send mail for purposes of monitoring. I recently setup a new server this week and configured unattended upgrades but I wanted a way to be notified in case of errors or in the event I needed to login to manually perform an action (reboot after a kernel upgrade, for example). I have used Postfix in the past but always ran into issues getting it to actually work.

Installing Postfix

First, we need to make sure we have Postfix installed on our server:

Debian/Ubuntu-based:
$ sudo apt install postfix
RHEL/Fedora-based:
sudo dnf install postfix
Arch-based:
sudo pacman -S postfix

During the process, it's going to ask you a series of questions. Since we are setting this up to be used with an external SMTP server we will select "Internet Site":

![Postfix Install - Step 1](/img/posts/2022/01/21/postfix-step1.jpg "Select "Internet Only"")

On the next screen, it's going to ask you for your "System mail name". This is the domain used in the email address(es) you'll be using. In my case, that's gradiian.io:

System mail name

You can verify Postfix is installed and running:

$ sudo systemctl status postfix
‚óŹ postfix.service - Postfix Mail Transport Agent
     Loaded: loaded (/lib/systemd/system/postfix.service; enabled; vendor preset: enabled)
     Active: active (exited) since Fri 2022-01-21 15:44:57 UTC; 1h 35min ago
    Process: 51586 ExecReload=/bin/true (code=exited, status=0/SUCCESS)
   Main PID: 4378 (code=exited, status=0/SUCCESS)

Here we can see Postfix is running and is also set to start on boot because it shows enabled. If for some reason it shows disabled instead, you can make it start on boot by enabling it:

$ sudo systemctl enable postfix

Configuring Postfix

After the installation completes, we now need to configure Postfix. First, let's start by creating the sasl_passwd file which will contain our credentials for our SMTP server.

$ sudo vim /etc/postfix/sasl_passwd

Inside this file, we'll include three pieces of information:

  • SMTP server address

  • SMTP username

  • SMTP password

You'll want to check with your mail provider to determine what your username is. For some hosts, this username is just the email address itself. If you're using some kind of two-factor authentication, you may need to create an app password to be able to login.

I use Namecheap as my mail host so that's what I'll be using in the following examples. If you use Namecheap, your username will be the full email address of the user you created when setting up the mailbox:

mail.privateemail.com    no-reply@gradiian.io:password

Next, we need to set the permissions on this file so that no one else can read it:

$ sudo chmod 600 /etc/postfix/sasl_passwd

For those who don't know, chmod 600 allows "read/write" (6) access to owner and no privileges to group (0) and other (0).

Now, we create a hashed database of this file which Postfix will use. The resulting file will be the same filename with an added .db extension.

$ sudo postmap /etc/postfix/sasl_passwd
$ ls -l /etc/postfix/sasl*
-rw------- 1 root root    69 Jan 21 16:12 /etc/postfix/sasl_passwd
-rw------- 1 root root 12288 Jan 21 16:12 /etc/postfix/sasl_passwd.db

Now, let's open the main Postfix configuration file:

$ sudo vim /etc/postfix/main.cf

In this file, we need to change a few existing options. First, let's set myhostname to the domain in our email:

myhostname = gradiian.io

Next, we need to remove our domain from the mydestination list if it's there otherwise mail will be sent locally on the server rather than through our SMTP server:

mydestination = localhost.localdomain, localhost

Lastly, we need to specify our relayhost which is going to be set to our SMTP server and port:

relayhost = mail.privateemail.com:587

At the bottom of main.cf we will add the following options to properly authenticate with our SMTP server:

smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_use_tls = yes
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt

Save and close the file. We'll now reload Postfix and try and send an email:

$ sudo systemctl reload postfix

Let's make sure mail is installed so we can send an email:

$ which mail
$

If you don't see the path to mail returned on the next line, you don't have mail installed. You can get this by installing the mailutils package:

$ sudo apt install mailutils

Let's try that again:

$ which mail
/usr/bin/mail

Perfect! The mail program has been installed and now we can test out our Postfix configuration.

Pro Tip: I found it much easier to troubleshoot my Postfix configuration by tailing the mail log in a separate window so I could watch the output of the logs for errors in real time: $ sudo tail -f /var/log/mail.log

To send a test email, we can use the previoulsy installed mail command to send a message. Replace receipient@domain.tld in the example below with the email address you wish to send the test email to:

$ echo "This is a test message" | mail -s "My clever subject" receipient@domain.tld

Let's check our mail log to see if that message was sent. If you aren't using tail to watch the output of the log, you can open it in a text editor and check the lines nearest the bottom:

$ vim /var/log/mail.log

Jan 21 16:49:09 localhost postfix/smtp[29150]: A0A04429E1: to=receipient@domain.tld, relay=mail.privateemail.com[198.54.122.135]:587, delay=1.3, delays=0.04/0.01/0.87/0.4, dsn=4.1.8, status=deferred (host mail.privateemail.com[198.54.122.135] said: 450 4.1.8 user@localhost: Sender address rejected: Domain not found (in reply to RCPT TO command))

Notice the last bit: said: 450 4.1.8 user@localhost: Sender address rejected: Domain not found. The server rejected our email because the sender address of user@localhost is invalid. We need to specify a From address that matches an existing email address on our mail host.

Rather than specifying a From address each time we want to send mail, we'll use Postfix's generic maps to rewrite our local address to our actual address.

$ sudo vim /etc/postfix/generic

Inside this file we'll not only map our current user but also for root which will allow mail from the system. Replace 'localhost' in this example with the value you set in main.cf for myhostname:

user@localhost    no-reply@gradiian.io
root@localhost    no-reply@gradiian.io

Now, when Postfix gets mail to send, it will map user@localhost to no-reply@gradiian.io. We need to generate a hash of this file as well:

$ sudo postmap /etc/postfix/generic

...and then we'll add it to the bottom of the Postfix configuration, /etc/postfix/main.cf:

smtp_generic_maps = hash:/etc/postfix/generic

Save and close the file and then reload Postfix:

$ sudo systemctl reload postfix

Now, go ahead and send an email again using the same command we used earlier. This time, the email should go through which you can verify in mail.log:

Jan 21 17:16:56 localhost postfix/qmgr[51600]: DAC79429E3: from=<user@localhost>, size=349, nrcpt=1 (queue active)
Jan 21 17:16:57 localhost postfix/smtp[56519]: DAC79429E3: to=<myself@mydomain.com>, relay=mail.privateemail.com[198.54.122.135]:587, delay=392, delays=391/0.02/0.87/0.21, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 2961C18000A2)
Jan 21 17:16:57 localhost postfix/qmgr[51600]: DAC79429E3: removed

Notice status=sent on the second line as well as 250 2.0.0 OK which is a reply back from the SMTP server letting us know that it has successfully sent our mail.

Update (July 6, 2022) - I was setting up a new server with a new email host and ran across an issue while trying to get email sending:

warning: SASL authentication failure: No worthy mechs found

SASL authentication failed; cannot authenticate to server ... : no mechanism available

You may need to install the package libsasl2-modules and reload postfix using systemctl reload postfix to be able to authenticate.