Skip to content
In this article8 sections

Many applications send email: a WordPress site after an order, a Laravel app after a signup, a monitoring tool when a disk fills. They need an SMTP server to hand that mail to. Running a full mail server for that job is more than most projects need, and the hard parts of self-hosted mail, such as IP reputation and reverse DNS, are the ones a relay avoids. A relay is a small Postfix service that accepts mail only from your own applications, authenticates to a mail provider, and passes the messages on. The provider handles delivery and reputation, and your server keeps the credentials and the queue.

This guide sets up that relay on Ubuntu 24.04. It covers the minimal Postfix configuration, storing the provider credentials safely, locking the relay so it cannot be abused, testing a message end to end, and reading the log when a message does not arrive.

Choose the provider first. The relay needs an account with an SMTP submission service: Amazon SES, a transactional email provider, or your own mailbox provider. The steps are the same for each. The host name, port and login come from the provider’s documentation. Port 587 with STARTTLS is the standard submission port.

Before you begin

  • An Ubuntu 24.04 server with a sudo user. A relay is light, so a small VPS is enough.
  • SMTP credentials from your provider: a host name, a port, a username and a password or access key.
  • Outbound port 587 open from the server. Many clouds block outbound port 25, but 587 is usually open. Check it with nc -vz smtp.example-provider.com 587.
  • A sending domain with SPF and DKIM set up at the provider. The provider usually gives you the records. The authentication records are covered in how to host your own email server.

Step 1: Install Postfix

sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt install -y postfix mailutils libsasl2-modules

Ubuntu asks how to configure Postfix when you install it interactively. Choose Internet Site if you are asked, and set the mail name to your server’s host name. You will overwrite the relevant settings in Step 3 anyway, so the choice is not critical.

Step 2: Store the provider credentials safely

Postfix reads the credentials from a map file. The file is a plain text list of the provider’s host and port, then the login. Put the host in square brackets, and the port after the brackets. The brackets stop Postfix from looking up MX records for the provider’s name, which is what you want when you send through a provider’s submission server:

sudo nano /etc/postfix/sasl_passwd
[smtp.example-provider.com]:587 [email protected]:the-smtp-password

Replace the host, login and password with the provider’s values. Then build the lookup database and lock the files so only root can read them:

sudo postmap /etc/postfix/sasl_passwd
sudo chown root:root /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
sudo chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db

Expected: sasl_passwd.db appears next to the text file. The plain text file still holds the password, so keep it at mode 600 and remove any copy you made in your home directory. The password is a credential with send rights. If it leaks, rotate it at the provider straight away.

Step 3: Configure Postfix as a relay

Open the main configuration file and set the relay values. Postfix’s configuration is key and value pairs, and these are the settings that make a relay:

sudo postconf -e "relayhost = [smtp.example-provider.com]:587"
sudo postconf -e "smtp_sasl_auth_enable = yes"
sudo postconf -e "smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd"
sudo postconf -e "smtp_sasl_security_options = noanonymous"
sudo postconf -e "smtp_tls_security_level = encrypt"
sudo postconf -e "smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt"
sudo postconf -e "inet_interfaces = loopback-only"
sudo postconf -e "mynetworks = 127.0.0.0/8 [::1]/128"

Read what each one does, because the two settings that protect you are the last two:

  • smtp_tls_security_level = encrypt refuses to send the password or the message unless the connection is encrypted. Use encrypt for a provider that offers TLS, which all the major ones do.
  • inet_interfaces = loopback-only makes Postfix listen on the local machine only. Applications on the same server can send mail, and the internet cannot.
  • mynetworks = 127.0.0.0/8 [::1]/128 names the only addresses allowed to send through the relay. Do not add a public address range here, because an open relay is abused within hours.

Check the effective configuration, then reload:

sudo postfix check && sudo postconf -n | grep -E "relayhost|sasl|tls_security|inet_interfaces|mynetworks"
sudo systemctl reload postfix

Expected: postfix check prints nothing, and the grep shows each value you set.

Step 4: Send a test message

Send a message to an address you can read, from the server itself. The sender must be on your sending domain, or the provider will reject it:

echo "Relay test from $(hostname) at $(date -Iseconds)" | mail -s "Relay test" -r [email protected] [email protected]

Watch the log to see what happened to it:

sudo tail -n 30 /var/log/mail.log

Expected: a line with relay=smtp.example-provider.com[...]:587 and status=sent, followed by the provider’s queue ID. If the log shows status=deferred, the message will be retried, and the reason is in the same line. If it shows status=bounced, the provider refused it, and the reason is usually the sender address or a missing DNS record. Check the message arrives in an inbox, and read its headers to confirm the SPF and DKIM checks passed.

Application code usually needs only three values, the host, the port and the sender address, and it submits through the relay. A typical PHP or Laravel setting points at 127.0.0.1 on port 25 without authentication, because the relay accepts mail only from the local machine.

Step 5: Make the relay safe to leave running

A relay that works on the first test can still fail later. Three habits prevent most problems:

  • Watch the queue. A growing queue means the provider is refusing mail, or the credentials have changed. Check it with mailq, and clear it only after you know why it grew.
  • Keep the sender domain authenticated. Mail that fails SPF or DKIM is delivered to spam or refused. Check the records again whenever you change providers or add a sending service.
  • Rotate the password on a schedule. Create a new key at the provider, update sasl_passwd, run postmap and reload, then revoke the old key.

Set up a basic alert on the queue, so a failure does not stay silent. A cron job that counts queued messages and writes to a log is enough for a small site:

*/15 * * * * [ "$(mailq | tail -n 1 | awk '{print $5}')" -gt 20 ] 2>/dev/null && logger -t relay-queue "mail queue above 20"

Change the number to suit your traffic, then read the relay-queue messages in the system log with journalctl -t relay-queue.

Common mistakes

  • Leaving out the brackets. Without them, Postfix looks up MX records for the provider’s name, and the connection can go to the wrong server.
  • Putting the port inside the brackets. The port goes after the closing bracket, as shown in Step 2.
  • Forgetting postmap. Postfix reads the database file, not the text file, so a changed password has no effect until you rebuild it.
  • Listening on all interfaces. inet_interfaces = all with a permissive mynetworks makes an open relay.
  • Using a personal mailbox password. Use a submission credential or an API key for the provider, so you can revoke it without touching the mailbox.

Frequently asked questions

Why not send directly from the application?

Sending directly from the server means your server’s IP address must have a good reputation and a correct reverse DNS record, which many VPS addresses lack. A relay hands delivery to a provider whose reputation is already established.

Is port 25 or 587 better?

Use 587 with STARTTLS for the connection to the provider. Port 25 is for server-to-server mail, and many clouds block it outbound. Your applications connect to the local relay on port 25, which is internal.

Can several applications share one relay?

Yes. Every application on the same server uses the same local relay, and the provider sees one sending account. If you need separate accounts for separate apps, use one provider credential per app and one relay per credential.

What about mail that the server receives?

This relay only sends. Inbound mail needs MX records and a mailbox server, which is a different setup covered in how to host your own email server.

A Postfix relay is a small, locked-down service with one job: accept mail from your own applications and hand it to a provider over an encrypted, authenticated connection. Keep the credentials at mode 600, listen only on loopback, restrict the relay to local addresses, and watch the queue. Test once, then leave it running.

Share this guide