When moving from Plesk to iRedMail (Postfix/Dovecot/Amavis stack), one critical feature often missing is the "redirect with saved copy" functionality. In Plesk, this was straightforward, but with Postfix using MySQL virtual alias maps, we need a more technical solution.
Your current configuration uses:
virtual_alias_maps = mysql:/etc/postfix/mysql_virtual_alias_maps.cf
With a typical query like:
SELECT destination FROM virtual_aliases WHERE source = '%s'
1. Recipient Delimiter + Catch-all
Modify your MySQL query to handle both forwarding and local delivery:
SELECT
CONCAT(original_user, '+forwarded@example.com') AS destination
FROM
virtual_aliases
WHERE
source = '%s'
UNION
SELECT
original_user AS destination
FROM
virtual_aliases
WHERE
source = '%s' AND keep_copy = 1
2. Using Transport Maps
Create a transport map for special handling:
# /etc/postfix/transport
save_and_forward@example.com local:user@example.com,smtp:forward@external.com
3. Advanced: Using Sender-Dependent Transport
Combine sender_dependent_relayhost_maps with transport_maps:
# postfix/main.cf
sender_dependent_relayhost_maps = mysql:/etc/postfix/mysql_sender_dependent.cf
transport_maps = mysql:/etc/postfix/mysql_transport_maps.cf
Here's a tested solution using recipient_canonical_maps:
# /etc/postfix/main.cf
recipient_canonical_maps = mysql:/etc/postfix/mysql_recipient_canonical.cf
# mysql_recipient_canonical.cf
user = postfix
password = yourpassword
hosts = 127.0.0.1
dbname = postfix
query = SELECT CONCAT('%u', ' ', '%u', ' ', destination)
FROM forwarding_table
WHERE source = '%s' AND keep_copy = 1
Extend your virtual_aliases table:
ALTER TABLE virtual_aliases
ADD COLUMN keep_copy TINYINT(1) DEFAULT 0,
ADD COLUMN delivery_method ENUM('local','remote') DEFAULT 'remote';
When implementing these solutions:
- Add proper indexes on source/destination columns
- Consider using MEMORY tables for high-volume forwarding
- Monitor postfix queue size during migration
Always check:
postmap -q user@domain.com mysql:/etc/postfix/mysql_virtual_alias_maps.cf
postconf -n | grep alias
tail -f /var/log/mail.log
When migrating from Plesk to iRedMail, one common pain point is replicating the "redirect with saved copy" functionality. The default Postfix + MySQL virtual alias implementation doesn't natively support this hybrid behavior where an email must both be forwarded externally AND delivered locally.
First, let's extend your MySQL table structure to accommodate the copy retention requirement. Add these columns to your aliases table:
ALTER TABLE virtual_aliases
ADD is_copy_retained TINYINT(1) DEFAULT 0,
ADD original_recipient VARCHAR(255) DEFAULT NULL;
Update your mysql_virtual_alias_maps.cf
to handle the new logic:
user = postfixadmin_user
password = your_secure_password
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT
CASE
WHEN is_copy_retained = 1 THEN CONCAT(original_recipient, ',', destination)
ELSE destination
END
FROM virtual_aliases
WHERE source = '%s' AND is_active = 1
For emails that need both forwarding and local copy, insert records like this:
INSERT INTO virtual_aliases
(domain, source, destination, is_copy_retained, original_recipient)
VALUES
('example.com', 'user@example.com', 'external@forward.com', 1, 'user@example.com');
Ensure your main.cf
includes these critical settings:
recipient_delimiter = +
virtual_alias_domains = mysql:/etc/postfix/mysql_virtual_domains.cf
virtual_alias_maps = mysql:/etc/postfix/mysql_virtual_alias_maps.cf
local_recipient_maps = $virtual_alias_maps
To prevent mail loops when forwarding back to your own domain, add this transport map:
/etc/postfix/transport:
example.com smtp:[127.0.0.1]
Then in main.cf
:
transport_maps = hash:/etc/postfix/transport
After implementing, test with:
postmap -q user@example.com mysql:/etc/postfix/mysql_virtual_alias_maps.cf
This should return both addresses separated by comma when copy retention is enabled.