When setting up a staging environment that mirrors production, email handling becomes a critical consideration. The goal is to prevent accidental email sends to real customers while maintaining identical application behavior. Postfix provides several ways to achieve this through its flexible configuration system.
There are three primary methods to implement this:
- Using
always_bcc
to duplicate messages to a file - Configuring a
virtual
mailbox transport - Setting up a
local
mailbox delivery
This is the simplest approach that works for most staging environments:
# /etc/postfix/main.cf
always_bcc = archive@localhost
# /etc/aliases
archive: "|/usr/bin/formail -a Date: >> /var/log/mail-archive.log"
# Then run:
sudo newaliases
sudo systemctl reload postfix
For more sophisticated setups where you want separate files per recipient:
# /etc/postfix/main.cf
virtual_mailbox_domains = localhost
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_minimum_uid = 1000
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000
# /etc/postfix/vmailbox
@localhost localhost/catchall/
# Create directory structure
sudo mkdir -p /var/mail/vhosts/localhost/catchall
sudo chown -R postfix:postfix /var/mail/vhosts
For maximum flexibility in processing messages:
# /etc/postfix/main.cf
mailbox_command = /usr/bin/procmail -a "$EXTENSION"
# ~/.procmailrc
:0 c
* ^To:.*@example.com
| /usr/local/bin/archive_email.sh
:0
/dev/null
After configuration, verify with:
sudo postconf -n
sudo systemctl restart postfix
echo "Test email" | mail -s "Test" user@localhost
tail -f /var/log/mail-archive.log
For better visualization of captured emails:
# Install mail viewing utility
sudo apt install mutt
# View the archive
mutt -f /var/log/mail-archive.log
To prevent disk space issues:
# /etc/cron.daily/mail-cleanup
#!/bin/sh
find /var/log/mail-archive.log -type f -mtime +30 -delete
When running a staging environment that mirrors production, one critical challenge is preventing accidental email sends to real customers. The PHP application in this case handles customer communications, and while we could modify the code to use dummy email addresses, maintaining identical code between staging and production is ideal for accurate testing.
The solution involves configuring Postfix to store emails locally rather than relaying them to external servers. Here's how to implement this on Debian/Ubuntu systems:
# Main configuration changes for /etc/postfix/main.cf inet_interfaces = loopback-only default_transport = error local_transport = virtual virtual_alias_maps = hash:/etc/postfix/virtual
Create a virtual aliases file to catch all outgoing emails:
# /etc/postfix/virtual @localhost staging-catchall @yourdomain.com staging-catchall
Then compile the aliases and restart Postfix:
sudo postmap /etc/postfix/virtual sudo systemctl restart postfix
To store emails in Maildir format for easy inspection:
# Add to /etc/postfix/main.cf virtual_mailbox_domains = localhost, yourdomain.com virtual_mailbox_base = /var/mail/vhosts virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox virtual_minimum_uid = 1000 virtual_uid_maps = static:5000 virtual_gid_maps = static:5000
For simpler needs, you can configure Postfix to only deliver to specific debugging peers:
# /etc/postfix/main.cf debug_peer_list = example.com default_transport = discard local_transport = discard relay_transport = discard
When using Maildir storage, you can view captured emails using standard mail clients or directly:
ls -l /var/mail/vhosts/yourdomain.com/staging-catchall/new cat /var/mail/vhosts/yourdomain.com/staging-catchall/new/*
For the mail queue approach mentioned, create a cron job to periodically flush the queue:
# In /etc/crontab 0 * * * * root postsuper -d ALL