Adapting a Postfix Mail Relay to Route Mail to Mailpit for Development
After setting up a Postfix mail relay for production notifications, I needed a way to intercept mail from development environments and send it to Mailpit instead of Mailgun. The goal: dev mails never hit the real relay, without changing application configs each time I switch environments.
Mailpit is a local SMTP server with a web UI - similar in concept to MailHog, but actively maintained and faster. Think of it as curl for email: you throw mail at it and inspect what came out. swaks (Swiss Army Knife for SMTP) is the ideal companion for testing this, covering auth, TLS, headers, and anything else you might want to verify.
sudo apt install swaks
swaks --version
Transport Maps: Route by Domain
The cleanest approach for domain-based routing is Postfix’s transport map. This lets you say “everything to test.local goes here, everything else goes to the default relayhost.”
First, add the transport map directive to /etc/postfix/main.cf:
sudo vim /etc/postfix/main.cf
transport_maps = hash:/etc/postfix/transport
Then create the transport rules in /etc/postfix/transport:
# Routes for Mailpit - all other mail goes to the default relayhost (e.g. Mailgun)
test.local smtp:[192.0.2.10]:1025
dev.local smtp:[192.0.2.10]:1025
@test.local smtp:[192.0.2.10]:1025
example.com smtp:[192.0.2.10]:1025
Replace 192.0.2.10 with the IP of the host running Mailpit. Note that test.local and @test.local are functionally equivalent - the @domain syntax is the explicit form for matching all addresses at a domain.
With this in place, mail to any of these addresses routes to Mailpit:
user@test.localadmin@test.localapp@dev.localtest@dev.local
Compile the map and reload Postfix:
sudo postmap /etc/postfix/transport
sudo systemctl reload postfix
Test it:
swaks --to test@test.local --server localhost
Check Mailpit’s web UI - the message should appear there rather than going through Mailgun.
Dedicated SMTP Port for Dev Traffic
If you want a cleaner separation - all traffic hitting a specific port goes to Mailpit, no domain matching needed - add a custom SMTP service in /etc/postfix/master.cf:
2525 inet n - n - - smtpd
-o relayhost=[192.0.2.10]:1025
This adds a listener on port 2525 that overrides the default relayhost for anything received on that port. The result:
- Port 25 → normal routing (Mailgun via your configured relayhost)
- Port 2525 → always Mailpit, regardless of recipient
Dev servers can then point at smtp://mailrelay.example.internal:2525 and every outbound mail automatically ends up in Mailpit. No per-application env vars, no conditional logic in app code.
Reload Postfix after the change:
sudo systemctl reload postfix
Sender-Based Routing
The third option is routing by sender address. This is useful if you have a fixed set of application accounts that should always use Mailpit, regardless of which host they send from or which port they connect to.
In /etc/postfix/main.cf:
sender_dependent_relayhost_maps = hash:/etc/postfix/sender_relay
Create /etc/postfix/sender_relay:
dev@myapp.local [192.0.2.10]:1025
@test.local [192.0.2.10]:1025
Compile and reload:
sudo postmap /etc/postfix/sender_relay
sudo systemctl reload postfix
Any mail from dev@myapp.local or any sender at @test.local gets routed to Mailpit, independent of the recipient or the port used.
Which Approach to Use
These three methods can be combined or used independently:
- Transport map - best when you control the recipient domain (local/fake TLDs for dev environments)
- Dedicated port - best when you control the application’s SMTP configuration but not the sender or recipient
- Sender-based routing - best when you have stable service accounts and want routing to follow the sender identity
For most homelab dev setups, the transport map is the easiest starting point. The dedicated port approach is cleaner if you’re running multiple dev services that might send to unpredictable addresses.