Sooner or later your VPS needs to send email. A cron job that fails wants to tell you. Fail2Ban wants to report a ban. Your Laravel or Django app wants to send a password reset. Netdata wants to alarm. The naive fix - point everything at localhost:25 and hope - almost never works on a modern VPS, because most providers block outbound port 25 and every big mailbox provider treats a fresh datacenter IP as guilty until proven innocent.
Running a full mail server to fix this is the wrong tool. You don't want to accept inbound mail, manage mailboxes, or babysit an IP reputation. You want the opposite: a small, send-only setup that hands every outgoing message to a real SMTP provider - Amazon SES, Mailgun, Postmark, Brevo, Fastmail, even a Gmail app password - and lets them deal with deliverability.
That's exactly what Postfix in "satellite" mode does. This guide installs it, wires it to a provider over authenticated TLS, rewrites the sender address so mail passes SPF and DKIM, routes root and cron mail to your real inbox, and tests the whole chain. About 20 minutes, and every process on the box can send mail that actually arrives.
relayhost to your provider's submission endpoint ([smtp.provider.com]:587)/etc/postfix/sasl_passwd, then postmap it and chmod 600root to your real email so cron and system warnings reach yousendmail and read /var/log/mail.log587, and a username + password (often an API key)Install Postfix along with the SASL modules it needs to authenticate to the provider:
sudo apt update
sudo apt install -y postfix libsasl2-modules mailutils
The installer shows a blue configuration screen. Two answers matter:
example.com.If you missed the dialog or want to redo it, run:
sudo dpkg-reconfigure postfix
At this point Postfix is running but not yet pointed at your provider. Confirm it's alive:
systemctl status postfix --no-pager
Open the main config:
sudo nano /etc/postfix/main.cf
Find and set the relayhost line - the square brackets tell Postfix to skip an MX lookup and connect directly to that host:
relayhost = [email-smtp.eu-west-1.amazonaws.com]:587
Swap in your provider's submission endpoint. A few common ones:
[email-smtp.<region>.amazonaws.com]:587[smtp.postmarkapp.com]:587[smtp.eu.mailgun.org]:587 (or the US host)[smtp-relay.brevo.com]:587[smtp.fastmail.com]:587[smtp.gmail.com]:587Now add the authentication and TLS block at the bottom of the same file:
# SASL authentication to the relay
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_mechanism_filter = plain, login
# Force TLS to the relay
smtp_tls_security_level = encrypt
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
# Only accept mail from this machine, never relay for the internet
inet_interfaces = loopback-only
mynetworks = 127.0.0.0/8 [::1]/128
The smtp_tls_security_level = encrypt line is important: it makes TLS mandatory, so Postfix refuses to send your password to a server that can't encrypt the connection.
Create the password file. The format is [host]:port username:password:
sudo nano /etc/postfix/sasl_passwd
[email-smtp.eu-west-1.amazonaws.com]:587 AKIAIOSFODNN7EXAMPLE:BsupdxxxxxxxxxxxxSecretKey
Use the exact same host and port you put in relayhost. For Gmail the username is your full address and the password is an app password, not your account password.
Now hash the file into the database Postfix reads, then lock the permissions:
sudo postmap /etc/postfix/sasl_passwd
sudo chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
The postmap command produces sasl_passwd.db next to it - that's the file Postfix actually uses. Any time you edit the plaintext version, re-run postmap.
By default, cron and system mail goes out as root@your-vps-hostname. Your provider has never heard of that address, so it fails their sender checks and either bounces or lands in spam. You need every message to appear to come from an address on your verified domain.
Set up a generic address map that rewrites local senders:
sudo nano /etc/postfix/generic
root@localhost [email protected]
root [email protected]
@your-vps-hostname [email protected]
Replace your-vps-hostname with the output of hostname -f. Hash it:
sudo postmap /etc/postfix/generic
Then tell Postfix to use it by adding this to main.cf:
smtp_generic_maps = hash:/etc/postfix/generic
Now outgoing mail carries [email protected] in the envelope and headers, which is an address your provider recognizes and signs with DKIM.
System warnings - a full disk, a failed cron job, a package that needs attention - are addressed to root. Forward them to an address you actually read:
sudo nano /etc/aliases
Add or edit the last line:
root: [email protected]
Rebuild the alias database:
sudo newaliases
Combined with the sender rewrite from Step 4, a message to root now goes out as [email protected] and arrives in your [email protected] inbox.
Reload Postfix so every change takes effect:
sudo systemctl restart postfix
Send yourself a test message. The mail command comes from the mailutils package you installed earlier:
echo "Relay is working from $(hostname -f)" | mail -s "VPS test" [email protected]
Immediately watch the log to see the handoff:
sudo tail -f /var/log/mail.log
A successful relay looks like this:
postfix/smtp[12043]: 4Kf9x2...: to=<[email protected]>,
relay=email-smtp.eu-west-1.amazonaws.com[52.x.x.x]:587,
delay=1.2, status=sent (250 Ok 0100019...)
status=sent with a 250 reply means the provider accepted the message. Check your inbox - and the spam folder the first time.
Handing mail to a reputable provider gets you most of the way, but the receiving mailbox still checks that your domain authorized that provider to send on its behalf. Add three records to your domain's DNS. The exact values come from your provider's dashboard - these are the shapes:
SPF - one TXT record on the root domain listing who may send. If you already have one, add the provider's include, don't create a second SPF record:
example.com. TXT "v=spf1 include:amazonses.com -all"
DKIM - your provider gives you one or more CNAME or TXT records to publish. They let the provider cryptographically sign your mail. SES, for example, hands you three CNAMEs; Postmark and Mailgun give you a TXT selector.
DMARC - a TXT record on _dmarc.example.com that tells mailboxes what to do with mail that fails the checks. Start in monitoring mode:
_dmarc.example.com. TXT "v=DMARC1; p=none; rua=mailto:[email protected]"
Verify the sender domain inside your provider's console before you rely on it - most require you to click "verify" once the DKIM records propagate. Once SPF and DKIM both pass, tighten DMARC to p=quarantine and later p=reject.
status=bounced ... 535 Authentication failed - the username or password in sasl_passwd is wrong, or you forgot to postmap after editing it. Re-check the credentials, re-run sudo postmap /etc/postfix/sasl_passwd, and restart Postfix.Connection timed out to port 587 - your provider is firewalling outbound 587, or you left the port as 25. Try the provider's alternate port (often 2587 or 465 with smtp_tls_wrapper_mode = yes).[email protected] or use mail-tester.com and read which check failed.Sender address rejected: not owned by user - the provider requires the From address to be verified. Confirm Step 4's rewrite is active (postconf smtp_generic_maps) and that [email protected] is verified in the dashboard.fatal: open database /etc/postfix/sasl_passwd.db: No such file - you never ran postmap. Run it, then restart Postfix.MAIL_MAILER=sendmail), Django, or any framework at the local Postfix instead of embedding SMTP credentials in every app's config. One relay, one place to rotate the key.A send-only Postfix relay is one of those small setups you configure once and forget, right up until the day a disk fills or a backup fails and the warning email actually lands in your inbox. That's the whole payoff: your server can finally talk to you.
Need a VPS that can reliably send its own mail? Our Linux plans include a static IP, IPv6, full root access, and generous bandwidth - everything a Postfix relay needs. See the options.