Baseline Setup Guide

Postfix + PostSRSd (SRS) + MariaDB (domains + aliases)

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:

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:

MX

example.org.   MX 10 mail.example.org.

A / AAAA

mail.example.org.  A     <YOUR_IPV4>
mail.example.org.  AAAA  <YOUR_IPV6>   # optional
example.org. TXT "v=spf1 ip4:<YOUR_IPV4> ip6:<YOUR_IPV6> -all"
_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

  1. Get domain id:
SELECT id, name FROM domain WHERE name='example.org';
  1. 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).


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_destination enabled at all times.

  • DB access: keep MariaDB bound to localhost and restrict permissions for /etc/postfix/mysql-*.cf.

Updated on