Skip to content

Self-Hosting a Production Mail Server (Postfix, Dovecot & DMARC)

Created: 2026-11-05 Last updated: 2026-11-06

Running your own email infrastructure is widely considered the supreme discipline of Linux system administration. Unlike a standalone web server, email is a globally federated system built on strict cryptographic trust, IP reputation, and multi-layered protocol verification.

While commercial providers (Gmail, Microsoft 365) scan metadata and lock you into proprietary ecosystems, operating your own hardened mail stack grants you 100% sovereignty over your private communications, unlimited aliases, custom server-side Sieve filtering, and zero third-party data mining.


1. Architecture, Protocols & Pre-Flight Requirements

An enterprise-grade Linux mail server is not a single monolithic binary—it is a modular pipeline of specialized daemons working in concert:

```mermaid

%%{init: {'flowchart': {'wrappingWidth': 350, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%% flowchart TB Sender["🌐 External Sender MTA
(Gmail / Corporate)"] Postfix["📮 Postfix Listener (:25)
TLS 1.3 · DNSBL · SPF"] Filter["🔐 Spam & Malware Filter
Rspamd & ClamAV"] Dovecot["📬 Dovecot LMTP
Server-Side Sieve"] Maildir[("💾 Encrypted Maildir
/var/vmail/domain/user/")]

    Sender ==>|"SMTP :25 (STARTTLS)"| Postfix
    Postfix -->|"Milter Scan"| Filter
    Filter -->|"Clean Email"| Dovecot
    Dovecot ==>|"Atomic Storage"| Maildir
```
%%{init: {'flowchart': {'wrappingWidth': 350, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
    Client["💻 Mail Client<br/>(Thunderbird / Mobile)"]

    subgraph InboundAccess ["Mailbox Access (IMAP)"]
        DovecotImap["📬 Dovecot IMAPS (:993)<br/>Encrypted Access"]
        MaildirStore[("💾 Maildir Storage<br/>/var/vmail/")]
        DovecotImap <==> MaildirStore
    end

    subgraph OutboundFlow ["Email Dispatch (SMTP)"]
        PostfixOut["📮 Postfix Submission (:587)<br/>SASL Auth + TLS 1.3"]
        DKIM["✍️ OpenDKIM Milter<br/>Cryptographic Signature"]
        Recipient["🌍 Recipient MTA (:25)<br/>Internet Delivery"]
        PostfixOut --> DKIM --> Recipient
    end

    Client <==>|"Fetch Mail (:993)"| InboundAccess
    Client ==>|"Send Mail (:587)"| OutboundFlow
    InboundAccess ==> OutboundFlow

The Four Core Building Blocks

  • MTA — Mail Transfer Agent (Postfix)


    The SMTP router. Accepts incoming messages on port 25, authenticates outbound user submissions on ports 587/465, enforces TLS, and routes mail across the internet via DNS MX lookups.

  • MDA & IMAP — (Dovecot)


    Stores incoming emails into structured Maildir folders, executes server-side Sieve sorting rules, serves mailboxes to clients via IMAPS (993), and acts as the central SASL authentication backend.

  • Trust & Crypto Layer (SPF / DKIM / DMARC)


    Cryptographically proves to Gmail, Outlook, and Yahoo that your server is authorized to send mail for your domain (SPF) and that headers/bodies were not forged in transit (DKIM + DMARC).

  • Anti-Spam & Malware (Amavis / Rspamd + ClamAV)


    Inspects every inbound message against real-time DNS blocklists (DNSBLs), Bayesian spam heuristics, and ClamAV virus signature databases before delivery.

Essential Network Ports Reference

Protocol / Role Port Transport & Encryption Purpose & Exposure
SMTP (Server-to-Server) 25/tcp Opportunistic STARTTLS Mandatory for receiving mail from other MTAs and sending outbound mail.
SMTPS (Submission Wrapper) 465/tcp Implicit TLS 1.3 (RFC 8314) Modern standard for mail clients (MUAs) submitting outgoing mail.
SMTP Submission 587/tcp Mandatory STARTTLS Classic authenticated port for mail clients submitting outgoing mail.
IMAPS (Mailbox Access) 993/tcp Implicit TLS 1.3 (RFC 8314) Encrypted mailbox synchronization for Thunderbird, iOS, Android, and Webmail.

Pre-Flight Checklist: Do Not Skip These 4 Requirements!

  1. Unblocked Port 25: Many cloud providers block outbound TCP port 25 by default to prevent spam. Verify outbound port 25 is open before starting:
    nc -zv -w 5 gmail-smtp-in.l.google.com 25
    
  2. Static Public IPv4 (and IPv6) Address: Dynamic residential IPs are listed on spam blocklists (Spamhaus PBL) and will be rejected by 99% of receiving mail servers.
  3. Forward-Confirmed Reverse DNS (FCrDNS / PTR): Your VPS provider's Reverse DNS (PTR) record for your IP address must resolve to mail.yourdomain.com, and mail.yourdomain.com (A/AAAA) must resolve back to that exact IP!
  4. Clean IP Reputation: Check your server's IP on MXToolbox Blacklist Check to ensure a previous VPS tenant didn't burn the IP's reputation.

Architecture Choice: Native Stack vs. Containerized Mail Suites

In this guide, we build the native Postfix + Dovecot stack step by step so you master every socket, config file, and protocol layer. If you later prefer a pre-packaged container suite for multi-domain management, check out Docker Mailserver (DMS), Mailcow, or the Rust-based Stalwart Mail Server.


2. DNS Foundation: A, AAAA, MX & PTR

Before installing a single package, configure your domain's DNS zone and your VPS provider's Reverse DNS (PTR). Replace yourdomain.com and 198.51.100.10 with your real domain and IP.

Record Type Name / Host Value / Target Priority TTL
A mail 198.51.100.10 — 3600
AAAA mail 2001:db8::10 (if IPv6 enabled) — 3600
MX @ (Root Domain) mail.yourdomain.com. 10 3600
PTR (at VPS Hoster) 198.51.100.10 mail.yourdomain.com. — —
Zone File Snippet for yourdomain.com
mail.yourdomain.com.    3600    IN    A       198.51.100.10
mail.yourdomain.com.    3600    IN    AAAA    2001:db8::10
yourdomain.com.         3600    IN    MX  10  mail.yourdomain.com.
Terminal: Verify Forward & Reverse DNS Resolution
# 1. Install dig utility if missing
sudo apt update && sudo apt install -y dnsutils

# 2. Verify A record and MX record
dig A mail.yourdomain.com +short
dig MX yourdomain.com +short

# 3. Verify Reverse DNS (PTR) matches your mail hostname!
dig -x 198.51.100.10 +short

3. Core Stack: Installing & Configuring Postfix & Dovecot

Step 1: Set the System Hostname & Install Packages

Your server's system hostname (FQDN) must match your PTR and A record (mail.yourdomain.com):

Terminal: Configure Hostname & Install Postfix + Dovecot
# 1. Set FQDN hostname
sudo hostnamectl set-hostname mail.yourdomain.com

# 2. Install Postfix, Dovecot (IMAP + LMTP + Sieve), and testing utilities
sudo apt update
sudo DEBIAN_PRIORITY=low apt install -y \
  postfix dovecot-core dovecot-imapd dovecot-lmtpd dovecot-sieve mailutils swaks

(During the Postfix prompt, select Internet Site and set the System mail name to yourdomain.com).

Step 2: Configure Postfix (main.cf) & Dovecot Mailboxes

Instead of outdated mbox single-file storage, we configure the resilient Maildir/ format (one atomic file per email) and set up Dovecot 2.3's native folder auto-creation (Trash, Sent, Drafts, Junk).

Terminal: Configure Postfix Basic Parameters
sudo postconf -e "myhostname = mail.yourdomain.com"
sudo postconf -e "mydomain = yourdomain.com"
sudo postconf -e "myorigin = \$mydomain"
sudo postconf -e "mydestination = \$myhostname, \$mydomain, localhost.\$mydomain, localhost"
sudo postconf -e "inet_interfaces = all"
sudo postconf -e "home_mailbox = Maildir/"
sudo postconf -e "recipient_delimiter = +"

Sub-Addressing (recipient_delimiter = +)

Setting recipient_delimiter = + automatically enables instant disposable aliases! Any mail sent to nejat+github@yourdomain.com or nejat+shopping@yourdomain.com is delivered directly into nejat@yourdomain.com.

Now configure Dovecot's Maildir path in /etc/dovecot/conf.d/10-mail.conf and verify native folder auto-creation in /etc/dovecot/conf.d/15-mailboxes.conf:

/etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:~/Maildir
/etc/dovecot/conf.d/15-mailboxes.conf (Native Dovecot 2.3+ Auto-Subscribe)
namespace inbox {
  mailbox Drafts {
    auto = subscribe
    special_use = \Drafts
  }
  mailbox Junk {
    auto = subscribe
    special_use = \Junk
  }
  mailbox Trash {
    auto = subscribe
    special_use = \Trash
  }
  mailbox Sent {
    auto = subscribe
    special_use = \Sent
  }
}

4. Transport Encryption (Let's Encrypt TLS 1.3) & Firewall

Never transmit passwords or emails in cleartext. We obtain a trusted Let's Encrypt certificate via certbot and enforce modern TLS 1.2 / TLS 1.3 across both Postfix and Dovecot.

Step 1: Open Firewall Ports & Obtain Certificate

Terminal: Configure UFW Firewall & Issue Let's Encrypt Certificate
# 1. Allow Mail Ports (25=SMTP, 465=SMTPS, 587=Submission, 993=IMAPS) & HTTP (for Certbot)
sudo ufw allow 25,80,465,587,993/tcp

# 2. Install Certbot and obtain certificate for mail.yourdomain.com
sudo apt install -y certbot
sudo certbot certonly --standalone -d mail.yourdomain.com \
  --non-interactive --agree-tos --email admin@yourdomain.com \
  --deploy-hook "systemctl reload postfix; systemctl reload dovecot"

Automatic Zero-Downtime Certificate Renewal

Adding --deploy-hook "systemctl reload postfix; systemctl reload dovecot" writes a permanent hook into /etc/letsencrypt/renewal/mail.yourdomain.com.conf. Every time Certbot renews your certificate in the background, Postfix and Dovecot automatically reload the new keys without dropping a single connection!

Step 2: Harden Postfix & Dovecot TLS Ciphers

Apply modern TLS settings to Postfix using postconf:

Terminal: Configure Hardened TLS 1.2/1.3 in Postfix
sudo postconf -e "smtpd_tls_cert_file = /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem"
sudo postconf -e "smtpd_tls_key_file = /etc/letsencrypt/live/mail.yourdomain.com/privkey.pem"
sudo postconf -e "smtpd_tls_security_level = may"
sudo postconf -e "smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1"
sudo postconf -e "smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1"
sudo postconf -e "smtpd_tls_mandatory_ciphers = high"
sudo postconf -e "smtp_tls_security_level = may"
sudo postconf -e "smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1"
sudo postconf -e "smtp_tls_CApath = /etc/ssl/certs"

And configure /etc/dovecot/conf.d/10-ssl.conf:

/etc/dovecot/conf.d/10-ssl.conf
ssl = required
ssl_cert = </etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.yourdomain.com/privkey.pem
ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes

5. SASL Authentication & Preventing Open Relays

An unauthenticated mail server that relays mail to external domains is an Open Relay—bots will discover it within minutes and flood the internet with spam, destroying your IP reputation.

Instead of maintaining two separate user databases, Postfix delegates SMTP authentication to Dovecot's SASL socket:

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    Client["💻 Mail Client (MUA)"] ==>|"1. Port 587/465"| Postfix["📮 Postfix smtpd"]
    Postfix <==>|"2. UNIX Socket"| DovecotAuth["🔐 Dovecot Auth Service"]
    DovecotAuth <==>|"3. Verify"| Backend[("🗄️ PAM / Virtual SQL DB")]
    Postfix ==>|"4. Auth OK"| Outbound(("🌍 Outbound Delivery"))

Step 1: Expose the Dovecot SASL Socket to Postfix

Edit /etc/dovecot/conf.d/10-auth.conf to allow plain and login over TLS:

/etc/dovecot/conf.d/10-auth.conf
disable_plaintext_auth = yes
auth_mechanisms = plain login

And enable the Postfix UNIX socket inside /etc/dovecot/conf.d/10-master.conf:

/etc/dovecot/conf.d/10-master.conf (Inside service auth block)
service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}

Step 2: Connect Postfix to Dovecot SASL & Enable Ports 587 / 465

Terminal: Configure Postfix SASL & Relay Restrictions
sudo postconf -e "smtpd_sasl_type = dovecot"
sudo postconf -e "smtpd_sasl_path = private/auth"
sudo postconf -e "smtpd_sasl_auth_enable = yes"
sudo postconf -e "smtpd_sasl_security_options = noanonymous"
sudo postconf -e "smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination"

Uncomment the submission (587) and smtps (465) blocks in /etc/postfix/master.cf:

/etc/postfix/master.cf (Enable Authenticated Submission Ports)
submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_tls_auth_only=yes
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject

smtps     inet  n       -       y       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
Terminal: Restart Postfix & Dovecot and Verify Socket
sudo systemctl restart dovecot postfix
sudo ls -la /var/spool/postfix/private/auth

Hands-On Lab 1: Testing Authenticated TLS Submission with swaks

Create a test user and verify end-to-end STARTTLS + SASL authentication on Port 587 using the Swiss Army Knife for SMTP (swaks):

Terminal: Verify Encrypted SASL Login & Message Queuing
# 1. Test STARTTLS + SASL Authentication on Port 587
swaks --server mail.yourdomain.com --port 587 \
      --tls \
      --auth LOGIN \
      --auth-user testuser \
      --auth-password 'YourStrongPassword' \
      --from testuser@yourdomain.com \
      --to external.recipient@gmail.com \
      --header "Subject: TLS + SASL Test from Sovereign Mail Server" \
      --body "Authenticated over TLS 1.3 via Dovecot SASL!"

# 2. Verify TLS 1.3 handshake on IMAPS Port 993
openssl s_client -connect mail.yourdomain.com:993 -servername mail.yourdomain.com </dev/null 2>/dev/null | grep -E "Protocol|Cipher|Verify return code"

6. Deliverability Mastery: SPF, DKIM & DMARC

Without cryptographic sender authentication, Gmail, Microsoft Outlook, and Apple iCloud will immediately route your outgoing emails to the Spam folder or reject them outright with 550-5.7.26.

How the Cryptographic Trust Triangle Works

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    Email["📧 Outgoing Email<br/>user@yourdomain.com"]
    RecvServer["🛡️ Receiving Mail Server<br/>Inbound Authentication"]

    SPF["1️⃣ SPF Check (DNS TXT)<br/>Authorized IP Check"]
    DKIM["2️⃣ DKIM Check (Crypto)<br/>Signature Match DNS"]
    DMARC["3️⃣ DMARC Alignment<br/>Policy Enforcement"]

    Inbox["🟢 Deliver to Inbox<br/>Authentication Passed"]
    Reject["🔴 Quarantine or Reject<br/>SMTP 550 Rejection"]

    Email ==>|"SMTP :25 Delivery"| RecvServer
    RecvServer --> SPF
    RecvServer --> DKIM
    SPF --> DMARC
    DKIM --> DMARC
    DMARC ==>|"PASS"| Inbox
    DMARC ==>|"FAIL"| Reject

Step 1: Publish Your SPF Record in DNS

Create a TXT record on your root domain (@ / yourdomain.com) that authorizes only your MX server and explicitly rejects (-all) all spoofed IPs:

DNS TXT Record: SPF Policy
Name:  @ (or yourdomain.com)
Type:  TXT
Value: "v=spf1 mx a:mail.yourdomain.com -all"

Step 2: Install & Configure OpenDKIM (2048-bit RSA Signing)

Terminal: Install OpenDKIM & Generate Cryptographic Keys
# 1. Install OpenDKIM and tools
sudo apt install -y opendkim opendkim-tools

# 2. Allow Postfix user to access the OpenDKIM socket inside the Postfix chroot
sudo usermod -aG opendkim postfix
sudo mkdir -p /var/spool/postfix/opendkim
sudo chown opendkim:postfix /var/spool/postfix/opendkim
sudo chmod 750 /var/spool/postfix/opendkim

# 3. Create key directory and generate a 2048-bit RSA key pair with selector 'default'
sudo mkdir -p /etc/opendkim/keys/yourdomain.com
sudo opendkim-genkey -b 2048 -d yourdomain.com -s default -D /etc/opendkim/keys/yourdomain.com/
sudo chown -R opendkim:opendkim /etc/opendkim
sudo chmod 600 /etc/opendkim/keys/yourdomain.com/default.private

Configure /etc/opendkim.conf to use the chroot-safe UNIX socket /var/spool/postfix/opendkim/opendkim.sock:

/etc/opendkim.conf
Syslog                  yes
UMask                   007
Mode                    sv
Canonicalization        relaxed/simple
Socket                  local:/var/spool/postfix/opendkim/opendkim.sock
UserID                  opendkim
PidFile                 /run/opendkim/opendkim.pid
KeyTable                refile:/etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable
InternalHosts           refile:/etc/opendkim/TrustedHosts
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts

Populate the three mapping tables:

Terminal: Create KeyTable, SigningTable, and TrustedHosts
cat << 'EOF' | sudo tee /etc/opendkim/KeyTable
default._domainkey.yourdomain.com yourdomain.com:default:/etc/opendkim/keys/yourdomain.com/default.private
EOF

cat << 'EOF' | sudo tee /etc/opendkim/SigningTable
*@yourdomain.com default._domainkey.yourdomain.com
EOF

cat << 'EOF' | sudo tee /etc/opendkim/TrustedHosts
127.0.0.1
localhost
::1
*.yourdomain.com
EOF

sudo chown opendkim:opendkim /etc/opendkim/KeyTable /etc/opendkim/SigningTable /etc/opendkim/TrustedHosts

Connect Postfix to the OpenDKIM milter:

Terminal: Wire OpenDKIM Milter into Postfix
sudo postconf -e "milter_default_action = accept"
sudo postconf -e "milter_protocol = 6"
sudo postconf -e "smtpd_milters = local:opendkim/opendkim.sock"
sudo postconf -e "non_smtpd_milters = \$smtpd_milters"

sudo systemctl restart opendkim postfix

Display your generated DKIM public key and add it as a TXT record under default._domainkey.yourdomain.com:

Terminal: Display Public DKIM DNS Record
sudo cat /etc/opendkim/keys/yourdomain.com/default.txt

Step 3: Publish Your DMARC Record in DNS

Create a TXT record for _dmarc.yourdomain.com. Start with p=quarantine (or p=none during initial testing) and transition to p=reject once verified:

DNS TXT Record: DMARC Policy
Name:  _dmarc
Type:  TXT
Value: "v=DMARC1; p=quarantine; rua=mailto:postmaster@yourdomain.com; adkim=r; aspf=r; pct=100"

7. Anti-Spam & Malware Defense Pipeline

An internet-facing port 25 receives constant automated spam and malware probes. We filter incoming mail through a multi-stage inspection pipeline:

%%{init: {'flowchart': {'wrappingWidth': 350}}}%%
flowchart TB
    In(("🌐 Port :25")) --> RBL["1️⃣ Postfix DNSBLs<br/>(Drop botnet IPs)"]
    RBL --> DKIM_V["2️⃣ OpenDKIM<br/>(Verify signature)"]
    DKIM_V --> AV["3️⃣ ClamAV Daemon<br/>(Block malware)"]
    AV --> Spam["4️⃣ SpamAssassin / Rspamd<br/>(Heuristic scoring)"]
    Spam --> Sieve["5️⃣ Dovecot Sieve<br/>(Move to Junk)"]
Terminal: Install Amavis, SpamAssassin & ClamAV
sudo apt install -y amavisd-new spamassassin clamav-daemon clamav-freshclam \
  pyzor razor libnet-dns-perl libmail-dkim-perl

# Grant ClamAV and Amavis mutual socket access
sudo adduser clamav amavis
sudo adduser amavis clamav

# Enable SpamAssassin rule updates and start services
sudo sa-update
sudo systemctl enable --now clamav-daemon clamav-freshclam spamassassin amavis

Enable virus and spam checks in /etc/amavis/conf.d/15-content_filter_mode:

/etc/amavis/conf.d/15-content_filter_mode
use strict;
@bypass_virus_checks_maps = (
   \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);
@bypass_spam_checks_maps = (
   \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);
1;

On servers with limited RAM, Rspamd (written in C) replaces both Amavis, SpamAssassin, and OpenDKIM in a single ultra-fast daemon with a built-in web dashboard (127.0.0.1:11334):

Terminal: Install Rspamd & Redis Backend
sudo apt install -y rspamd redis-server
sudo postconf -e "smtpd_milters = inet:127.0.0.1:11332"
sudo postconf -e "non_smtpd_milters = inet:127.0.0.1:11332"
sudo postconf -e "milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}"
sudo systemctl restart rspamd postfix

Hands-On Lab 2: Testing Anti-Spam (GTUBE) & Anti-Virus (EICAR) Detection

Never test filters with real malware! Use the industry-standard, harmless GTUBE (spam trigger) and EICAR (antivirus trigger) test strings:

Terminal: Fire GTUBE & EICAR Test Payloads
# 1. Test Spam Filter with the standard GTUBE signature
swaks --to testuser@yourdomain.com --from spamtest@example.org --server localhost \
      --header "Subject: GTUBE Spam Filter Test" \
      --body "XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X"

# 2. Test ClamAV Antivirus with the standard EICAR signature
swaks --to testuser@yourdomain.com --from virustest@example.org --server localhost \
      --header "Subject: EICAR Antivirus Test" \
      --body 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*'

# 3. Inspect /var/log/mail.log to verify detection
sudo tail -n 25 /var/log/mail.log

8. Webmail Access with Roundcube

To access your mailboxes from any browser with a modern interface, deploy Roundcube Webmail connected locally to Dovecot (IMAPS :993) and Postfix (Submission :587):

Since your VPS already runs Nginx, running Roundcube in an isolated container bound to 127.0.0.1:8090 keeps PHP and Apache dependencies off your host OS:

~/roundcube-stack/docker-compose.yml
services:
  roundcube:
    image: docker.io/roundcube/roundcubemail:latest
    container_name: roundcube-webmail
    restart: unless-stopped
    ports:
      - "127.0.0.1:8090:80"
    environment:
      - ROUNDCUBEMAIL_DEFAULT_HOST=ssl://mail.yourdomain.com
      - ROUNDCUBEMAIL_DEFAULT_PORT=993
      - ROUNDCUBEMAIL_SMTP_SERVER=tls://mail.yourdomain.com
      - ROUNDCUBEMAIL_SMTP_PORT=587
      - ROUNDCUBEMAIL_UPLOAD_MAX_FILESIZE=25M
    volumes:
      - roundcube_db:/var/roundcube/db
      - roundcube_config:/var/roundcube/config

volumes:
  roundcube_db:
  roundcube_config:

Terminal: Install Roundcube Natively with MariaDB
sudo apt install -y roundcube roundcube-mysql roundcube-plugins
In /etc/roundcube/config.inc.php, set:
/etc/roundcube/config.inc.php
$config['imap_host'] = 'ssl://mail.yourdomain.com:993';
$config['smtp_host'] = 'tls://mail.yourdomain.com:587';
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';


9. Operations: Queue Management, Logs & Troubleshooting

Postfix Queue Management Cheat Sheet

Command Action & Purpose
mailq or postqueue -p List all messages currently waiting in the active, deferred, or hold queues.
sudo postcat -q <QUEUE_ID> Inspect the full headers and body of a specific queued message.
sudo postqueue -f Flush the queue: immediately retry delivery for all deferred messages.
sudo postsuper -h <QUEUE_ID> Place a suspicious message on hold so Postfix stops retrying it.
sudo postsuper -d <QUEUE_ID> Permanently delete a specific message from the queue.
sudo postsuper -d ALL deferred Emergency cleanup: delete all deferred messages from the queue.

Common Troubleshooting Scenarios

1. Gmail Rejects Outgoing Mail with 550-5.7.1 or 550-5.7.26

Cause: Google strictly enforces two requirements for all senders: 1. Valid Forward-Confirmed Reverse DNS (PTR) matching your EHLO hostname (mail.yourdomain.com). 2. Passing SPF (v=spf1 ...) AND DKIM (2048-bit RSA) signatures with a published _dmarc policy.

Fix: Test your live score at mail-tester.com and verify dig -x <YOUR_IP> +short returns mail.yourdomain.com..

2. Mail Client Cannot Send: SASL authentication failure: cannot connect to Dovecot auth

Cause: Postfix runs inside a chroot jail at /var/spool/postfix on Debian/Ubuntu. If Dovecot creates its socket at /var/run/dovecot/auth-client instead of inside the Postfix chroot, Postfix cannot reach it.

Fix: Ensure /etc/dovecot/conf.d/10-master.conf creates the listener at /var/spool/postfix/private/auth with user = postfix, group = postfix, and mode = 0660.

3. Outbound Emails Stay Stuck in deferred Queue (Connection timed out on port 25)

Cause: Your cloud/VPS provider has a platform-level firewall blocking outbound TCP port 25.

Fix: Open a support ticket with your VPS provider (e.g., Hetzner, Netcup, DigitalOcean) to request unblocking outbound Port 25 after verifying your account, or configure an authenticated SMTP Relay (relayhost = [smtp.relayprovider.com]:587) in main.cf.

4. How Do I Verify My Server achieves a 10/10 Deliverability Score?

Run this quick verification checklist from your terminal:

Terminal: Complete DNS Auth Audit
dig TXT yourdomain.com +short              # Verify SPF
dig TXT default._domainkey.yourdomain.com +short  # Verify DKIM Public Key
dig TXT _dmarc.yourdomain.com +short       # Verify DMARC Policy