Postfix as a Smarthost Relay to a Real ESP
Keep local sendmail and cron working, but hand the public hop to SES, Mailgun, or Postmark. Postfix sasl_passwd and smtp_tls are the whole design.
Netbay Developer Relations
Netbay Engineering
On this page
Direct delivery from a VPS IP is the hard path. The easy path that still feels like local mail is a smarthost: Postfix accepts mail from the app on loopback, then submits it over TLS to Amazon SES, Mailgun, Postmark, or another ESP on port 587. Cron, PHP mail(), and nodemailer keep talking to 127.0.0.1. The ESP owns reputation, throttling, and the public MX conversation.
Netbay does not operate a managed SMTP product. This pattern is you plus a third-party ESP, running on a Lucknow VPS you control.
Why a smarthost instead of an SDK
SDKs are fine when one application sends. A box also has unattended, logwatch, certbot, and a leftover WordPress. Those processes know sendmail, not boto3. Postfix as the local submission agent captures all of them, adds a queue, and retries on ESP outages without you rewriting cron.
The ESP still requires SPF include, a DKIM record they give you (or Easy DKIM), and a verified From domain. You do not skip DNS auth by relaying. You skip sending from a cold datacenter IP on port 25.
Credentials and TLS
Create an SMTP user at the ESP (SES IAM SMTP credentials, Mailgun SMTP, Postmark server token). Store it in a Postfix sasl_passwd map, not in the app repo.
# /etc/postfix/sasl_passwd (mode 600)
[email-smtp.ap-south-1.amazonaws.com]:587 AKIAEXAMPLE:smtpSecretHere
postmap /etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.dbBrackets around the hostname stop Postfix from MX-looking up the submission endpoint. Use the regional endpoint the ESP documents. The example host is an SES submission name; replace it with Mailgun's smtp.mailgun.org or Postmark's smtp.postmarkapp.com if that is what you bought.
main.cf for submission relay
Keep the daemon on loopback. Do not offer open relay.
# /etc/postfix/main.cf excerpts
myhostname = mail.example.com
inet_interfaces = loopback-only
mydestination = $myhostname, localhost.$mydomain, localhost
relayhost = [email-smtp.ap-south-1.amazonaws.com]: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
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_loglevel = 1
smtp_tls_note_starttls_offer = yes
smtp_sasl_tls_security_options = noanonymoussmtp_tls_security_level = encrypt refuses to send in the clear. may is not good enough for credentials. After postfix reload, send a test with printf and sendmail, then read mail.log for a 235 authentication succeeded and a 250 from the ESP.
If the ESP requires a specific envelope sender, set sender_canonical_maps or smtp_generic_maps so cron's root@mail.example.com becomes alerts@example.com, a domain you verified. Unverified From is the usual SES 554.
What not to double-sign
If the ESP DKIM-signs on the way out, you can leave local OpenDKIM off and publish the ESP's CNAME selectors. Two signatures are legal, but two selectors to operate is extra work. Pick one signer: either the ESP (simpler) or your OpenDKIM (useful when several ESPs send as you). SPF must include the ESP, not only your VPS IP. The VPS is no longer the public SMTP client.
Failures live in two queues
Postfix deferred is "could not talk to the ESP." The ESP's bounce dashboard is "the ESP talked to Gmail and Gmail said 550." Your app will not see the second class unless you consume webhooks or parse a bounce mailbox. Treat the smarthost as transport, not as delivery receipts.
When mail sits in the deferred queue, postqueue -p and postcat on the queue id tell you whether SASL failed, TLS failed, or the ESP rejected the envelope sender. A 535 is almost always a bad password map (postmap not run, or the file still mode 644 on a system that refuses to read it). A 554 Message rejected: Email address is not verified is SES telling you the From domain is not in the verified identities list.
Keep the VPS off the public MX conversation. inet_interfaces = loopback-only plus a host firewall that does not publish 25 is the correct shape for a sender-only box. If you also receive mail on this host, split receive (port 25 from the world) and send (smarthost on 587) in master.cf rather than mixing relayhost with an open smtpd.
Takeaway
A loopback Postfix plus an authenticated 587 relay is how a VPS sends mail without pretending its IP is warm. Publish the ESP's SPF include and DKIM, encrypt the hop, and read bounce webhooks. You can spin up an Ubuntu 24.04 instance on Netbay in under 60 seconds and wire this smarthost on a static Lucknow IP — netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance