When dealing with email delivery through Postfix, you might encounter situations where specific destination mail servers (identified by their MX records) require special routing. Common scenarios include:
- Bypassing corporate firewalls for Microsoft 365/O365 traffic
- Using alternative delivery paths for high-volume email providers
- Implementing custom routing for compliance requirements
To implement MX-based relay routing, we'll need to leverage Postfix's transport_maps
mechanism combined with a custom policy script. Unlike sender_dependent_relayhost_maps
which operates on sender addresses, this solution acts on resolved MX records.
Here's the complete workflow we'll implement:
1. Postfix performs DNS MX lookup for recipient domain
2. Custom policy script checks MX record against our rules
3. Based on MX match, returns appropriate transport mapping
4. Postfix routes message through designated relay host
1. Create the Transport Map File
First, create a transport map file (e.g., /etc/postfix/mx_transport
):
# Format: mx_pattern transport:relay_host
outlook.com smtp:[some_smtp.example.com]
.protection.outlook.com smtp:[alternative-relay.example.com]
* local:
2. Create the Policy Script
Save this as /usr/libexec/postfix/mx_policy
:
#!/bin/sh
# Read the MX record from STDIN
read mx_record
# Match against our patterns
case "$mx_record" in
*outlook.com*)
echo "smtp:[some_smtp.example.com]"
;;
*protection.outlook.com*)
echo "smtp:[alternative-relay.example.com]"
;;
*)
echo "local:"
;;
esac
exit 0
Make it executable:
chmod +x /usr/libexec/postfix/mx_policy
3. Configure Postfix
Add these to your main.cf
:
# Enable transport maps
transport_maps = pcre:/etc/postfix/mx_transport
# Custom MX resolution policy
smtpd_policy_service_default_action = DUNNO
smtpd_policy_service_time_limit = 3600s
policy-spf_time_limit = 3600s
smtpd_recipient_restrictions =
check_policy_service unix:private/mx_policy
... [other restrictions] ...
4. Create the Master Service Entry
Add to master.cf
:
mx_policy unix - n n - - spawn
user=nobody argv=/usr/libexec/postfix/mx_policy
Verify your configuration:
postmap -q "microsoft-com.mail.protection.outlook.com" pcre:/etc/postfix/mx_transport
echo "microsoft-com.mail.protection.outlook.com" | /usr/libexec/postfix/mx_policy
For more complex routing, you can use PCRE patterns in your transport file:
# Case-insensitive matching for various Outlook MX patterns
/^(.*\.)?outlook\.com$/i smtp:[outlook-relay.example.com]
/^.*\.protection\.outlook\.com$/i smtp:[o365-relay.example.com]
/^.*\.google(mail)?\.com$/i smtp:[gmail-relay.example.com]
- Cache MX lookups using Postfix's
address_verify_cache
- Consider implementing a daemon policy server for high-volume environments
- Monitor your mail logs for "mx_policy" timeout warnings
Check these logs when debugging:
grep 'mx_policy' /var/log/maillog
postconf -n | grep -E 'transport|policy'
postqueue -p
When dealing with complex email routing scenarios in Postfix, we often encounter situations where certain destination domains require special handling. The specific case here involves routing emails through different SMTP relays based on the recipient domain's MX records, particularly for domains like Outlook.com that might have specific delivery requirements.
The default Postfix relayhost
parameter applies globally to all outgoing mail, while sender_dependent_relayhost_maps
only considers the sender address. Neither solution addresses our need for destination-based routing.
Postfix's transport maps provide the perfect mechanism for destination-based routing. Here's how to implement it:
# main.cf additions
transport_maps = hash:/etc/postfix/transport
default_transport = smtp
First, create /etc/postfix/transport
with content like:
# Handle Outlook.com domains
.outlook.com relay:[some_smtp.example.com]
.protection.outlook.com relay:[some_smtp.example.com]
# Default route (catch-all)
* smtp:
For true MX-based matching, we need to combine transport maps with policy services. Here's a more advanced solution:
# main.cf
smtpd_recipient_restrictions =
check_recipient_mx_access hash:/etc/postfix/mx_access
# mx_access file
microsoft-com.mail.protection.outlook.com RELAY=[some_smtp.example.com]
For maximum flexibility, consider a policy script that performs real-time MX lookups:
# policy script example (Python)
import socket
def mx_lookup(domain):
try:
return sorted(socket.getaddrinfo(domain, None, socket.AF_INET))
except socket.gaierror:
return []
def policy_filter(recipient):
domain = recipient.split('@')[-1]
mx_hosts = mx_lookup(domain)
for mx in mx_hosts:
if 'outlook.com' in mx[4][0]:
return "RELAY=[some_smtp.example.com]"
return "DUNNO"
Always test your configuration with:
postmap /etc/postfix/transport
postfix reload
postfix flush
Verify routing decisions with:
postmap -q "microsoft-com.mail.protection.outlook.com" hash:/etc/postfix/transport
When implementing MX-based routing:
- Cache MX lookups to avoid DNS overhead
- Set proper TTL values in your transport maps
- Consider using memcache for large-scale deployments
If emails aren't routing correctly:
- Check Postfix logs (
/var/log/mail.log
) - Verify transport map syntax with
postmap
- Test MX lookups manually with
dig MX example.com