The internet is full of "don't self-host email, just use SES" advice. For production transactional volume at scale, that's correct. For a personal domain, a small SaaS doing hundreds of mails/day, or a homelab you actually want to trust — self-hosting on a VPS works fine. You just have to do it right.
"Right" means six things: choose a clean IP, publish correct DNS, authenticate properly, warm up the IP, rate-limit yourself, and measure placement. Skip any one and you end up in Spam.
1. Pick a VPS provider with a clean mail IP
The cheap cloud providers have mixed reputations. Before you commit:
- Do an IP reputation check on the IP you were assigned. Use
dig +short -x <ip>, Spamhaus, UCEPROTECT, Barracuda, Microsoft SNDS. - Some providers block port 25 outbound by default: DigitalOcean, Google Cloud, AWS EC2, Azure. Port 25 block = you can't send direct to MX; you must relay.
- Good providers for outbound mail: Hetzner, OVH, Contabo, Vultr (with request), Linode (with ticket). Port 25 open after a short verification.
# Quick IP reputation check before you start
$ dig +short -x 203.0.113.10
# Should return something. If empty, no PTR set - you'll need to set one.
# Check common blacklists (or use a web tool)
$ for zone in zen.spamhaus.org bl.spamcop.net b.barracudacentral.org; do
> result=$(dig +short $(echo 203.0.113.10 | awk -F. '{print $4"."$3"."$2"."$1}').$zone)
> [ -z "$result" ] && echo "$zone: clean" || echo "$zone: LISTED ($result)"
> done
# If listed on delivery: request removal via the list's form BEFORE
# sending any mail. Some lists require proof of ownership.2. Hostname and PTR match
Your VPS must announce a HELO hostname that matches its forward and reverse DNS. This is FCrDNS and it's the single biggest signal Gmail uses to decide if you're a legitimate mail server.
# On the VPS
$ hostnamectl set-hostname mail.yourdomain.com
$ echo '127.0.1.1 mail.yourdomain.com mail' >> /etc/hosts
$ systemctl restart postfix
# Forward A record (at your DNS host)
mail.yourdomain.com. IN A 203.0.113.10
# Reverse PTR (at your VPS provider's control panel)
# 203.0.113.10 -> mail.yourdomain.com
# Verify both match (FCrDNS)
$ dig +short mail.yourdomain.com # -> 203.0.113.10
$ dig +short -x 203.0.113.10 # -> mail.yourdomain.com.If the reverse returns the VPS provider's generic hostname (static.203.0.113.10.ovh.net), change it at the provider's panel to your mail hostname. This is the #1 fixable issue on self-hosted VPS mail.
3. SPF, DKIM, DMARC
Authentication is non-negotiable. All three records, correctly aligned:
; SPF - lock to your server IP
yourdomain.com. IN TXT "v=spf1 ip4:203.0.113.10 -all"
; DKIM - generate 2048-bit key
; $ opendkim-genkey -d yourdomain.com -s mail -b 2048
mail._domainkey.yourdomain.com. IN TXT
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A..."
; DMARC - start p=none with reports
_dmarc.yourdomain.com. IN TXT
"v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; adkim=s; aspf=s; pct=100"
; After 2 weeks of clean reports, tighten:
; "v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com; ..."
; After another 2 weeks:
; "v=DMARC1; p=reject; rua=mailto:dmarc@yourdomain.com; ..."Send a mail to check-auth@verifier.port25.com and read the reply. It shows you SPF, DKIM, DMARC and SpamAssassin from one place. If DMARC alignment shows fail, you've got a From domain that doesn't match your DKIM signing domain — fix that before sending anywhere else.
4. Minimal Postfix + OpenDKIM config
# /etc/postfix/main.cf - minimal outbound-only config
myhostname = mail.yourdomain.com
mydomain = yourdomain.com
myorigin = $mydomain
inet_interfaces = all
inet_protocols = ipv4
mydestination = $myhostname, localhost.$mydomain, localhost
mynetworks = 127.0.0.0/8 [::1]/128
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem
smtpd_tls_security_level = may
smtp_tls_security_level = may
# OpenDKIM milter
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = inet:127.0.0.1:8891
milter_default_action = accept
# Sanity restrictions
smtpd_helo_required = yes
smtpd_helo_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_invalid_helo_hostname
smtpd_relay_restrictions =
permit_mynetworks
permit_sasl_authenticated
reject_unauth_destination
# Hide version
smtpd_banner = $myhostname ESMTP
disable_vrfy_command = yes5. Warm up the IP
A brand-new IP has zero sending reputation. Gmail and Microsoft apply extra scrutiny for the first 2-4 weeks. Ramp volume gradually:
- Week 1: 50 mails/day total.
- Week 2: 200/day.
- Week 3: 500/day.
- Week 4: 1500/day.
- Week 5+: scale as engagement allows.
Prioritize engaged recipients: your team, existing customers who opened mail recently, double-opt-in subscribers. Do not blast 1000 cold addresses on day one — that's how you earn a Spamhaus listing before your coffee's cold.
Add smtp_destination_rate_delay = 2s anddefault_destination_concurrency_limit = 5 tomain.cf. Gmail treats burst senders worse than steady ones. A 2-second pacer and 5 concurrent connections per destination domain looks human.
6. Feedback loops and postmaster tools
Once mail is flowing, instrument it:
- Google Postmaster Tools: verify
yourdomain.com, watch IP reputation, domain reputation, authentication pass rates, spam rate. Data appears after you cross a daily volume threshold (~500 mails/day). - Microsoft SNDS / JMRP: for Outlook/Hotmail, sign up at
sendersupport.olc.protection.outlook.com. Get IP reputation + feedback loop for complaints. - DMARC aggregate reports: your
ruaaddress gets daily XML reports from every major provider. Parse with a tool like DMARC Analyzer or self-host Parsedmarc. - Seed-mailbox placement tests: weekly, against 20+ addresses across providers. Catches drift before real users complain.
The VPS mail checklist
- IP clean on Spamhaus, UCEPROTECT, Barracuda at provisioning time.
- Port 25 outbound open at the provider.
- Hostname, forward A, PTR all match: FCrDNS green.
- SPF, DKIM (2048-bit), DMARC published and aligned.
- Postfix + OpenDKIM wired with TLS and rate-limiting.
- IP warmup plan for 4 weeks.
- Postmaster Tools / SNDS / DMARC reporting on.
- Seed-placement test baseline and weekly monitoring.
When not to self-host on a VPS
Self-hosting is fine for low-to-mid volume (up to a few thousand mails/day) with stable, engaged audiences. It's a bad idea for:
- Cold-outbound sales sequences — you'll burn the IP fast.
- High-stakes transactional mail at scale — use Postmark, SES, Mailgun.
- Teams without a sysadmin who can respond to a Spamhaus listing within 24 hours.
- Compliance-heavy industries — providers have DPAs and certifications.
Frequently asked questions
Can I host mail on AWS Lightsail or EC2?
What about IPv6?
inet_protocols = ipv4 in Postfix to force IPv4.