How to send email from a VPS through an SMTP relay

Send your server's mail (alerts, cron output, app email) through an SMTP relay on port 587 with Postfix, so it's delivered without running a full mail server.

Tested on Debian 13 Stable

Before you start

  • A server running Ubuntu or Debian (AlmaLinux and Rocky work with dnf instead of apt)
  • Root or a user with sudo
  • An account with an SMTP relay or email provider and its SMTP host and login
  • A domain you can add DNS records to

To send email from a VPS, hand it to an SMTP relay instead of delivering it yourself: install Postfix as a "satellite system", set relayhost = [smtp.your-relay.com]:587, give it the relay's username and password in /etc/postfix/sasl_passwd, and require TLS. Every program on the server that sends mail through sendmail or localhost:25 then goes through the relay.

A relay is any provider that accepts mail from you over an authenticated connection and delivers it: transactional email services, or the SMTP server of the mailbox provider you already use. They deliver from addresses with an established reputation, which is why mail sent this way reaches inboxes rather than spam folders. It also works when outbound port 25 is blocked, as it is on many VPS providers, because relays accept mail on ports 587 and 465.

We configured and checked this on Debian 13 (Postfix 3.10), including the queue and the log; our test relay was a hostname that doesn't exist, so the last step (actual delivery) depends on your provider's details.

What you need from the relay

From your relay's documentation or dashboard, note:

Example
SMTP host smtp.relay.example
Port 587 (STARTTLS) or 465 (TLS from the start)
Username Often your account email, or a literal word like apikey
Password An SMTP password or API key, not your account password where they're separate
Allowed sender The domain or address you've verified with them, such as alerts@example.com

Most relays require you to verify your sending domain by adding DNS records (SPF and DKIM). Do that in their dashboard first; it's what makes the mail trusted.

Install Postfix

bash
sudo apt update
sudo apt install -y postfix libsasl2-modules mailutils

The installer asks a few questions:

  1. General type of mail configuration: choose Satellite system.
  2. System mail name: your server's full name or your domain, for example web-1.example.com. It's stored in /etc/mailname and used for mail from local users like root.
  3. If it asks for the SMTP relay host, enter [smtp.relay.example]:587. You can also set it in the next step.

libsasl2-modules lets Postfix log in to the relay; mailutils gives you the mail command for testing. On AlmaLinux and Rocky Linux, install postfix cyrus-sasl-plain s-nail with dnf and edit the same settings with postconf.

Point Postfix at the relay

Set the relay and the login options. The square brackets tell Postfix to connect to that host directly rather than look up its MX records:

bash
sudo postconf -e \
  'relayhost = [smtp.relay.example]:587' \
  'smtp_sasl_auth_enable = yes' \
  'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd' \
  'smtp_sasl_security_options = noanonymous' \
  'smtp_tls_security_level = encrypt' \
  'inet_interfaces = loopback-only'

What these do: log in with the password from a file, never without a login, refuse to send unless the connection is encrypted (so the password never crosses the network in clear), and accept mail only from the server itself, so you don't run an open relay.

For a relay on port 465 instead, use relayhost = [smtp.relay.example]:465 and add smtp_tls_wrappermode = yes.

Store the login, readable by root only, and build the lookup table Postfix reads:

bash
echo '[smtp.relay.example]:587 USERNAME:PASSWORD' | sudo tee /etc/postfix/sasl_passwd >/dev/null
sudo chmod 600 /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd

The host and port must match relayhost exactly, brackets included. Run postmap again whenever you change the file.

Check the configuration and restart:

bash
sudo postfix check
sudo systemctl restart postfix

postfix check prints nothing when all is well.

Send a test message

bash
echo "Test from $(hostname)" | mail -s "Relay test" -r alerts@example.com you@example.net

-r sets the sender. Use an address in the domain you verified with the relay; most relays reject anything else.

Check it worked

Watch Postfix's log while the message goes out:

bash
sudo journalctl -t postfix/smtp -n 20

A delivered message shows status=sent (250 ...) with the relay's name. Then look in the recipient's inbox, and open the message's headers ("Show original"): SPF: PASS and DKIM: PASS mean the domain setup is right.

Messages that couldn't be sent wait in the queue and are retried:

bash
mailq

Use it from your apps

  • Anything that calls sendmail (cron's output, mail, PHP's mail(), Netdata alerts, unattended-upgrades reports) now goes through the relay with no further setup.
  • Apps with their own SMTP settings can use localhost, port 25, no login and no TLS, and let Postfix handle the relay; or skip Postfix and give the app the relay's details directly.

To make mail for root (cron errors, system alerts) reach you, add a forward:

bash
echo 'root: you@example.net' | sudo tee -a /etc/aliases
sudo newaliases

Troubleshooting

status=deferred (Host or domain name not found ...). The relay's hostname is wrong in relayhost. Fix it, update sasl_passwd to match, postmap it, and sudo postqueue -f to retry the queue.

SASL authentication failed or 535 Authentication credentials invalid. Wrong username or password, or the line in sasl_passwd doesn't match relayhost exactly. Check, postmap again, sudo systemctl reload postfix.

no mechanism available in the log. The SASL modules aren't installed: sudo apt install libsasl2-modules.

Sender address rejected or 550 ... not verified. The relay doesn't accept that From. Send from your verified domain (-r for mail; the "from" setting in your app). To rewrite root@web-1.example.com for system mail, see Postfix's smtp_generic_maps.

connect to smtp.relay.example[...]:25: Connection timed out. Postfix is using port 25 because relayhost has no port. Add :587.

To clear test messages out of the queue: sudo postsuper -d ALL.

Next steps

Doing it on VPSNine

New VPSNine servers can't send on outbound port 25, which is why this tutorial uses 587. Relays on 587 and 465 work out of the box. If you need to run your own mail server instead, read Sending email: port 25 and set reverse DNS before you ask us to open it. Runs on Debian 13 and every other image, on all plans.

Be first when orders open

Join the waitlist and we'll send you one email when VPSNine launches. Pick a plan if you already know which one you want, and we'll size the first servers around it.

  • One email, on launch day
  • Monthly or yearly, no lock-in
  • 14-day money-back guarantee