This Base Stack (maintained by a member of Haltman.io) deploys a minimal, production-oriented mail forwarding infrastructure:
-
Postfix: receives inbound mail for hosted domains and forwards via alias rules
-
PostSRSd (SRS): rewrites envelope sender to reduce SPF/DMARC breakage during forwarding
-
MariaDB: stores accepted domains and alias routing (
address → goto)
Scope: Base Stack only (no Node.js/API here).
PTR / Reverse DNS (do this first)
For outbound deliverability, configure PTR at your provider:
-
IPv4 ->mail.example.com(PTR record / reverse DNS) -
mail.example.com-> IPv4(A record / forward DNS)
Notes:
-
PTR is set at your VPS/provider, not in Cloudflare.
-
Use a hostname you control (e.g.,
mail.example.com).
Requirements
-
Linux server (tested on Debian 13 / Trixie)
-
Public IPv4 (IPv6 optional)
-
TCP port 25 inbound + outbound
-
Domain + DNS control
Install (Debian)
sudo apt update
sudo apt install -y mariadb-server postfix postfix-mysql postsrsd
DNS (for each hosted mail domain)
Replace:
-
example.orgwith your domain -
mail.example.orgwith your mail host -
<YOUR_IPV4>/<YOUR_IPV6>with your server IP(s)
MX
example.org. MX 10 mail.example.org.
A / AAAA
mail.example.org. A <YOUR_IPV4>
mail.example.org. AAAA <YOUR_IPV6> # optional
SPF (recommended)
example.org. TXT "v=spf1 ip4:<YOUR_IPV4> ip6:<YOUR_IPV6> -all"
DMARC (recommended starter)
_dmarc.example.org. TXT "v=DMARC1; p=none; rua=mailto:dmarc@example.org; fo=1"
MariaDB setup
1) Create DB + user
sudo mysql
CREATE DATABASE maildb;
CREATE USER 'db_username'@'localhost' IDENTIFIED BY 'db_p4ssw0rd';
GRANT SELECT, INSERT ON maildb.* TO 'db_username'@'localhost';
FLUSH PRIVILEGES;
2) Create tables
USE maildb;
CREATE TABLE `domain` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`active` tinyint(1) DEFAULT 1,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `alias` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`address` varchar(255) NOT NULL,
`goto` varchar(255) NOT NULL,
`active` tinyint(1) DEFAULT 1,
`domain_id` int(11) NOT NULL,
`created` timestamp NULL DEFAULT current_timestamp(),
`modified` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `uq_alias_address` (`address`),
KEY `address` (`address`),
KEY `domain_id` (`domain_id`),
CONSTRAINT `alias_ibfk_1` FOREIGN KEY (`domain_id`) REFERENCES `domain` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3) Insert your first domain
INSERT INTO domain (name, active) VALUES ('example.org', 1);
Postfix MySQL lookup files
1) Aliases map
Create /etc/postfix/mysql-virtual-aliases.cf:
user = db_username
password = db_p4ssw0rd
hosts = 127.0.0.1
dbname = maildb
query = SELECT goto FROM alias WHERE address='%s' AND active=1
Permissions:
sudo chmod 640 /etc/postfix/mysql-virtual-aliases.cf
sudo chown root:postfix /etc/postfix/mysql-virtual-aliases.cf
2) Domains map
Create /etc/postfix/mysql-virtual-domains.cf:
user = db_username
password = db_p4ssw0rd
hosts = 127.0.0.1
dbname = maildb
query = SELECT 1 FROM domain WHERE name='%s' AND active=1
Permissions:
sudo chmod 640 /etc/postfix/mysql-virtual-domains.cf
sudo chown root:postfix /etc/postfix/mysql-virtual-domains.cf
PostSRSd setup (SRS)
Edit /etc/default/postsrsd:
SRS_DOMAIN=example.org
SRS_SECRET=/etc/postsrsd.secret
SRS_EXCLUDE_DOMAINS=
SRS_SEPARATOR=+
Generate secret:
sudo openssl rand -hex 32 | sudo tee /etc/postsrsd.secret >/dev/null
sudo chmod 600 /etc/postsrsd.secret
Enable + start:
sudo systemctl enable postsrsd
sudo systemctl restart postsrsd
sudo systemctl status postsrsd --no-pager
Postfix configuration (Forwarding + SQL + SRS)
Edit /etc/postfix/main.cf and set/ensure:
myhostname = mail.example.org
mydomain = example.org
myorigin = $mydomain
inet_interfaces = all
inet_protocols = ipv4
# Not a local mailbox server
mydestination =
# Accept domains and route aliases from MariaDB
virtual_alias_domains = mysql:/etc/postfix/mysql-virtual-domains.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-aliases.cf
# Anti-open-relay (required)
smtpd_relay_restrictions = permit_mynetworks, reject_unauth_destination
smtpd_recipient_restrictions = reject_unauth_destination
# SRS (PostSRSd)
sender_canonical_maps = tcp:localhost:10001
sender_canonical_classes = envelope_sender
recipient_canonical_maps = tcp:localhost:10002
recipient_canonical_classes = envelope_recipient
Reload:
sudo postfix reload
sudo systemctl status postfix --no-pager
Operational usage (add domains and aliases)
Add a domain
INSERT INTO domain (name, active) VALUES ('new-domain.tld', 1);
Add an alias
- Get domain id:
SELECT id, name FROM domain WHERE name='example.org';
- Insert alias:
INSERT INTO alias (address, goto, active, domain_id)
VALUES ('alias@example.org', 'destination@somewhere.tld', 1, <DOMAIN_ID>);
Postfix picks changes immediately (no restart required).
Quick validation (recommended)
Test MariaDB lookups as Postfix sees them:
postmap -q example.org mysql:/etc/postfix/mysql-virtual-domains.cf
postmap -q alias@example.org mysql:/etc/postfix/mysql-virtual-aliases.cf
Watch logs:
journalctl -u postfix -f
Common problems / notes
-
Outbound mail rejected: usually missing PTR, port 25 blocked, weak SPF/DMARC, or bad IP reputation.
-
Relay misconfiguration risk: keep
reject_unauth_destinationenabled at all times. -
DB access: keep MariaDB bound to localhost and restrict permissions for
/etc/postfix/mysql-*.cf.