Configure Postfix on Ubuntu to Send Email from Your Application

Technical guide to configure Postfix on Ubuntu as an SMTP relay, with SPF, DKIM and DMARC, to deliver transactional email from your application.

Sending email from your application looks simple until the first message hits Gmail’s spam folder or gets rejected by Outlook. Postfix is the most-used SMTP server on Ubuntu because it’s stable, performant and well documented, but the default configuration falls far short of what modern providers demand to accept your messages.

This guide is for developers who need to configure Postfix on an Ubuntu 24.04 LTS VPS to send transactional email — signup confirmations, password resets, order notifications. We’ll configure Postfix as an authenticated relay, set up SPF, DKIM and DMARC in DNS, and validate real delivery. Estimated time: 40 to 60 minutes, depending on DNS propagation.

The approach here is the “null client” pattern, sending via an external authenticated relay. It’s the correct configuration for 95% of web application use cases: simpler, safer, and with significantly better deliverability than trying to send straight from the VPS.

Prerequisites

Before starting, make sure you have the environment ready and the relay credentials at hand.

Prerequisites

Ubuntu 24.04 LTS with sudo access, your own domain with DNS control (Cloudflare, your registrar, etc), and credentials from an external SMTP relay (Amazon SES, Mailgun, SendGrid or similar). The relay must have your sending domain validated.

System Ubuntu 24.04 LTS
Packages postfix, libsasl2-modules, opendkim
Outbound ports 587 (submission) or 465 (smtps)
DNS Access to create TXT and CNAME

Also check that the VPS hostname is configured correctly — Postfix uses the hostname to identify itself in HELO/EHLO, and if it’s generic like ubuntu-vps relays may reject it.

hostnamectl set-hostname mail.yourdomain.com
echo "127.0.1.1 mail.yourdomain.com mail" | sudo tee -a /etc/hosts

Installing Postfix

The default Postfix installation on Ubuntu covers almost everything we need, but the initial debconf configuration asks for a few important choices.

01

Update the package index and install Postfix along with the SASL modules required for authentication against the external relay:

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

During installation, debconf will open a blue screen asking for the configuration type. Choose Internet Site with smarthost. Then set the “system mail name” to yourdomain.com (without subdomain).

02

If you went past the configuration screen without paying attention, run the wizard again:

sudo dpkg-reconfigure postfix

Select Internet Site with smarthost, set the mail name to yourdomain.com, and the SMTP relay host to [smtp.yourprovider.com]:587 (with brackets, which disable MX lookup).

Configuring the authenticated relay

Postfix needs to know where to send and how to authenticate. This lives in two files: main.cf (main configuration) and sasl_passwd (credentials).

03

Edit /etc/postfix/main.cf and add or adjust the following directives at the end of the file:

sudo nano /etc/postfix/main.cf
relayhost = [smtp.yourprovider.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_sasl_tls_security_options = noanonymous
header_size_limit = 4096000

The directive smtp_tls_security_level = encrypt forces TLS on outbound — without it, your SASL credentials could go in plaintext if the relay accepts plaintext.

04

Create the credentials file with the relay username and password:

sudo nano /etc/postfix/sasl_passwd

Add a single line in the format:

[smtp.yourprovider.com]:587 API_USER:API_PASSWORD

Save, compile to a binary hash and lock the file down — it contains plaintext credentials:

sudo postmap /etc/postfix/sasl_passwd
sudo chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db
sudo systemctl restart postfix
Credentials in backups

Remember to exclude /etc/postfix/sasl_passwd (plaintext) from public backups or version control. Keep only the .db file if you need to restore — it contains the hash Postfix uses.

Configuring SPF, DKIM and DMARC in DNS

Without DNS authentication, no serious provider accepts your email consistently. We’ll configure the three essential records.

SPF (Sender Policy Framework)

SPF declares which servers can send email with your domain as the sender. Create a TXT record in your domain’s DNS:

Type: TXT
Name: @
Value: v=spf1 include:_spf.yourprovider.com -all

The -all at the end is a hard fail — any server outside the include is rejected. In production, use ~all (soft fail) during the first few days and migrate to -all after validating deliverability.

DKIM (DomainKeys Identified Mail)

DKIM cryptographically signs every email sent. Most relays provide the public key ready to paste into DNS — usually as CNAME or TXT. Grab the values from your relay provider’s dashboard and create the records as instructed.

If you need Postfix itself to sign (rare in a null client setup), install opendkim:

05

Install and configure OpenDKIM only if your relay does not sign automatically:

sudo apt install -y opendkim opendkim-tools
sudo mkdir -p /etc/opendkim/keys/yourdomain.com
cd /etc/opendkim/keys/yourdomain.com
sudo opendkim-genkey -s default -d yourdomain.com
sudo chown opendkim:opendkim default.private

The generated default.txt file contains the TXT record you need to publish in DNS at default._domainkey.yourdomain.com.

DMARC

DMARC instructs the receiving server what to do with emails that fail SPF or DKIM. Start in monitoring mode:

Type: TXT
Name: _dmarc
Value: v=DMARC1; p=none; rua=mailto:[email protected]; pct=100

After a week of collecting reports and confirming that SPF and DKIM pass consistently, migrate p=none to p=quarantine and then p=reject.

Verifying the configuration

Before plugging this into the application, validate with manual tests that everything works.

06

Send a test email to a Gmail address (which offers a detailed authentication report):

echo "Test email content" | mail -s "Postfix Relay Test" \
  -a "From: [email protected]" [email protected]

Follow the log in real time on another SSH session to see the full SMTP conversation:

sudo tail -f /var/log/mail.log

You should see lines like status=sent (250 2.0.0 OK). Any deferred or bounced indicates a problem.

07

Open the email in Gmail, click the three dots and select “Show original”. Look for the header lines:

SPF: PASS
DKIM: PASS
DMARC: PASS

All three need to be PASS. If any is FAIL or NEUTRAL, review the corresponding DNS record and wait for propagation (up to 24 hours on some providers).

Quick validation tool

For more detailed tests, send an email to [email protected] or use the mail-tester.com site. Both return a complete report with SPF, DKIM, DMARC, reverse DNS and spam score.

Integrating with your application

With Postfix working as an authenticated null client, your application sends email via SMTP localhost on port 25 without needing to know the relay credentials.

Example environment variable configuration

SMTP_HOST=127.0.0.1
SMTP_PORT=25
SMTP_USER=
SMTP_PASS=
MAIL_FROM=[email protected]

Frameworks like Laravel, Django, Rails and Node.js (nodemailer) can send email pointing at 127.0.0.1:25 without local authentication — Postfix takes care of the authenticated relay to the external provider.

Troubleshooting

Messages stuck in deferred

If postqueue -p shows messages with deferred status, it’s usually a SASL authentication or TLS issue:

sudo postqueue -p
sudo postcat -vq <MESSAGE_ID>

Check /var/log/mail.log for the specific error message. The most common cases: wrong relay password (535 Authentication failed), TLS rejected by the relay (STARTTLS failed), or VPS IP blocked by the relay (check your provider dashboard).

”Relay access denied” error

This means you’re trying to send with a sender address outside the authorized domain on the relay. Confirm the application’s From: uses exactly the domain configured at the provider — and that the domain is validated in the dashboard.

Postfix not listening on port 25

If your application can’t connect to 127.0.0.1:25, confirm Postfix is listening:

sudo ss -tlnp | grep :25

If nothing shows up, edit /etc/postfix/main.cf and confirm inet_interfaces = loopback-only or inet_interfaces = all (for null client, loopback-only is correct and safer). Restart with sudo systemctl restart postfix.

Next steps

With Postfix configured, it’s worth exploring more robust production settings:

  • Configure postfix-pflogsumm to receive daily reports by email about send volume, bounces and suspicious attempts.
  • Implement rate limiting via smtpd_client_connection_rate_limit to protect against applications with infinite send loops.
  • Use a job queue (Sidekiq, Celery, Laravel Queue) to enqueue sends instead of calling sendmail synchronously — this prevents HTTP requests from stalling when the relay is slow.
  • Monitor your domain reputation monthly with tools like Google Postmaster Tools and Microsoft SNDS.

If you’re rolling transactional email into production, a Hostini VPS ships with a dedicated IPv4 and reverse DNS configurable from the dashboard — two basic requirements for any email provider to take your sending seriously.

Frequently asked questions

Can I send email directly from the VPS without using an external relay?

Technically yes, but delivery will suffer. Most cloud IP providers sit on low-reputation lists by default, and Gmail/Outlook frequently send them straight to spam or reject them outright. For low transactional volumes (up to a few hundred per day), use an authenticated relay like Amazon SES, Mailgun or SendGrid. The local Postfix stays as just a gateway.

What's the difference between Postfix as a full MTA and as a null client?

A full MTA accepts inbound email and delivers it to local users or other domains — you need this if you host mailboxes. A null client (or send-only) only sends locally generated email to an external relay. For web applications dispatching transactional email, null client is the correct standard: smaller attack surface, simpler configuration.

Why do my emails go to spam even with valid SPF, DKIM and DMARC?

Authentication solves identity, not reputation. If the VPS IP is on blacklists (Spamhaus, Barracuda) or has a bad history, even authenticated email goes to spam. Check reputation at mxtoolbox.com and consider using an authenticated relay with a dedicated IP, especially during the first 30 days of sending.

Postfix is running but mail won't send. How do I debug?

Start with journalctl -u postfix -n 100 and /var/log/mail.log for recent messages. Use postqueue -p to see the stuck queue and postcat -vq ID to inspect a specific message. If you see deferred with connection refused, the external relay is blocking or your SASL credentials are wrong — check /etc/postfix/sasl_passwd.

Do I need to open port 25 in the firewall?

If you use an external relay (port 587 or 465 outbound), port 25 doesn't need to be open in the firewall — only authenticated outbound matters. If you run a full MTA receiving email, then port 25 inbound is required. For a null client on a VPS, keep 25 closed on the inbound side and only open 587/465 outbound.

How do I run a send test without needing a full SMTP client?

Use the sendmail command directly: echo 'Subject: test' | sendmail -v [email protected]. The -v flag shows the full SMTP conversation in real time, useful for identifying handshake, authentication and remote server response. Alternative: swaks --to [email protected] --from [email protected].

Topics:
Next steps Ryzen cloud with NVMe storage and always-on DDoS protection.Go live on a Hostini VPS →
Was this tutorial helpful?
Chat on WhatsApp