Email server administration dashboard on a monitor beside a server rack with blinking LEDs
Concept visualization: a mail server admin console next to the rack that hosts it. Generated illustration, not a photo of the actual setup.
2025 Infrastructure GitHub ↗

Overview

This project is a fully self-hosted email infrastructure using Postfix as the SMTP server and Dovecot for IMAP access, deployed on a VPS with the custom domain tyfsadik.org. The goal was to eliminate reliance on Gmail or Proton while aiming for reliable delivery to major providers.

Deliverability is supported through a complete set of DNS-based email authentication records: DKIM (DomainKeys Identified Mail) signs every outbound message with a 2048-bit RSA key, SPF (Sender Policy Framework) authorizes the server's IP, and DMARC enforces policy alignment and provides aggregate reporting. TLS is mandatory on the submission port and IMAP via Let's Encrypt certificates, and opportunistic on port 25 as SMTP standards require. Inbox placement also depends on IP reputation and recipient policies, so DMARC reports and queue health are monitored on an ongoing basis.

On ports: outbound client mail uses the submission port (587) with STARTTLS rather than the legacy SMTPS port 465, so modern clients work with standard settings. Port 25 stays opportunistic-TLS-only because MX-to-MX mail must accept peers that cannot negotiate TLS; making TLS mandatory there would break inbound mail.

Architecture

graph LR subgraph Inbound["Inbound Mail"] IN["Internet\n(sender MTA)"] -->|"port 25 MX lookup"| PF["Postfix SMTP\nport 25"] PF -->|"local delivery\nMaildir"| DV["Dovecot\nMailbox Store"] end subgraph Outbound["Outbound Mail"] MC["Mail Client\n(Thunderbird/mobile)"] -->|"IMAP :993"| DV MC -->|"SMTP submission\n:587 STARTTLS"| PF PF -->|"OpenDKIM signs"| DK["DKIM Milter"] DK -->|"signed + TLS"| OUT["Recipient MX\n(Gmail, Outlook...)"] end subgraph DNS["DNS Records (tyfsadik.org)"] MX_R["MX record\nmail.tyfsadik.org"] SPF_R["SPF TXT\nv=spf1 ip4:x.x.x.x -all"] DKIM_R["DKIM TXT\nmail._domainkey"] DMARC_R["DMARC TXT\n_dmarc policy"] PTR_R["PTR record\nreverse DNS"] end style PF fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style DV fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style DK fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style MC fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style IN fill:#181818,stroke:#1e1e1e,color:#888 style OUT fill:#181818,stroke:#1e1e1e,color:#888 style MX_R fill:#181818,stroke:#1e1e1e,color:#888 style SPF_R fill:#181818,stroke:#1e1e1e,color:#888 style DKIM_R fill:#181818,stroke:#1e1e1e,color:#888 style DMARC_R fill:#181818,stroke:#1e1e1e,color:#888 style PTR_R fill:#181818,stroke:#1e1e1e,color:#888

Tech Stack

  • Postfix, MTA handling SMTP reception (port 25) and submission (port 587)
  • Dovecot, IMAP server with Maildir storage and SASL authentication
  • OpenDKIM, DKIM signing milter integrated with Postfix
  • SPF / DMARC, DNS TXT records for sender authorization and policy enforcement
  • Let's Encrypt / Certbot, TLS certificates for SMTP and IMAP
  • Linux (Debian 12), VPS host operating system
  • Thunderbird / mobile clients, IMAP clients for reading and sending mail

Build Process

1

Install and Configure Postfix

Postfix is installed from apt and configured for internet site operation. The main.cf file sets the hostname, domain, mailbox format (Maildir), and enables SASL authentication for the submission port. The master.cf file activates the submission service on port 587 with mandatory STARTTLS.

apt install postfix -y
# Select "Internet Site" during setup, set domain to tyfsadik.org

# /etc/postfix/main.cf key settings:
myhostname = mail.tyfsadik.org
mydomain = tyfsadik.org
myorigin = $mydomain
inet_interfaces = all
mydestination = $myhostname, localhost.$mydomain, $mydomain
home_mailbox = Maildir/
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.tyfsadik.org/fullchain.pem
smtpd_tls_key_file  = /etc/letsencrypt/live/mail.tyfsadik.org/privkey.pem
# Opportunistic TLS on port 25 (required for MX-to-MX mail); mandatory on submission
smtpd_tls_security_level = may
# SASL stays off globally and is enabled only on the submission port (see master.cf)
smtpd_sasl_auth_enable = no
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
# Relay only for local networks and authenticated submission clients
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

# Enable submission port in /etc/postfix/master.cf:
submission inet n - y - - smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject

systemctl restart postfix
2

Configure Dovecot IMAP

Dovecot is configured to use Maildir storage, authenticate against system users, provide IMAP on port 993 with SSL, and expose a SASL socket that Postfix uses for submission authentication. Three key config files are modified.

apt install dovecot-core dovecot-imapd -y

# /etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:~/Maildir

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

# /etc/dovecot/conf.d/10-ssl.conf
ssl = required
ssl_cert = </etc/letsencrypt/live/mail.tyfsadik.org/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.tyfsadik.org/privkey.pem

# /etc/dovecot/conf.d/10-master.conf (SASL socket for Postfix)
service auth {
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660
    user = postfix
    group = postfix
  }
}

systemctl restart dovecot
3

Provision TLS with Let's Encrypt

TLS certificates are provisioned for mail.tyfsadik.org using certbot in standalone mode (since Nginx is not running on this server). A deploy hook restarts Postfix and Dovecot automatically on renewal so they pick up the refreshed certificate.

apt install certbot -y

certbot certonly --standalone \
  -d mail.tyfsadik.org \
  --agree-tos --email [email protected]

# /etc/letsencrypt/renewal-hooks/deploy/restart-mail.sh
#!/bin/bash
systemctl restart postfix dovecot

chmod +x /etc/letsencrypt/renewal-hooks/deploy/restart-mail.sh

# Test renewal
certbot renew --dry-run
4

Configure OpenDKIM

OpenDKIM is installed, a 2048-bit RSA signing key is generated for the mail selector, and the public key is published as a DNS TXT record. Postfix is connected to the OpenDKIM milter so every outbound message is signed before delivery.

apt install opendkim opendkim-tools -y

# Generate a 2048-bit key pair (opendkim-genkey defaults to 1024-bit)
mkdir -p /etc/opendkim/keys/tyfsadik.org
opendkim-genkey -s mail -d tyfsadik.org -b 2048 \
  -D /etc/opendkim/keys/tyfsadik.org/
chown opendkim:opendkim /etc/opendkim/keys/tyfsadik.org/mail.private

# /etc/opendkim.conf (key settings)
Domain                  tyfsadik.org
KeyFile                 /etc/opendkim/keys/tyfsadik.org/mail.private
Selector                mail
Socket                  inet:12301@localhost

# /etc/postfix/main.cf (add milter)
milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:localhost:12301
non_smtpd_milters = $smtpd_milters

# Publish the DNS TXT record from:
cat /etc/opendkim/keys/tyfsadik.org/mail.txt

systemctl enable opendkim && systemctl restart opendkim postfix
5

Add SPF and DMARC DNS Records

SPF, DMARC, and MX records are added in the DNS control panel for tyfsadik.org. The PTR (reverse DNS) record is set at the VPS provider to match the mail hostname, which is critical for avoiding spam classification.

# DNS records to add in domain registrar / DNS panel:

# MX record
tyfsadik.org.      IN  MX  10  mail.tyfsadik.org.

# A record for mail subdomain
mail.tyfsadik.org. IN  A   x.x.x.x

# SPF TXT (authorize only this server's IP)
tyfsadik.org.      IN  TXT "v=spf1 ip4:x.x.x.x -all"

# DKIM TXT (copy public key from mail.txt)
mail._domainkey.tyfsadik.org. IN TXT "v=DKIM1; k=rsa; p=MIIBIj..."

# DMARC TXT (report only first, then reject)
_dmarc.tyfsadik.org. IN TXT "v=DMARC1; p=reject; rua=mailto:[email protected]"

# PTR record (set at VPS provider control panel)
x.x.x.x  -->  mail.tyfsadik.org
6

Test with mail-tester.com and swaks

The full email stack is tested by sending a test message to a mail-tester.com address and reviewing the report for authentication gaps. Swaks is used for command-line SMTP testing to verify SASL authentication, TLS negotiation, and DKIM signing without a mail client. Note that SASL authenticates against system user accounts as configured in Step 2, so the auth username is the system login, not the full email address.

apt install swaks -y

# Send a test email via the submission port with auth
# (auth-user is the system account; use that account's password)
swaks -to [email protected] \
  -from [email protected] \
  -server mail.tyfsadik.org:587 \
  -auth LOGIN \
  -auth-user taki \
  -auth-password 'yourpassword' \
  -tls

# Verify DKIM header in received message
# Look for: DKIM-Signature: v=1; a=rsa-sha256; d=tyfsadik.org; s=mail;

# Check mail logs for delivery status
tail -f /var/log/mail.log

Request Flow

sequenceDiagram participant MC as Mail Client participant PF as Postfix :587 participant DK as OpenDKIM milter participant RMX as Recipient MX participant DV as Dovecot IMAP MC-->>PF: SMTP EHLO + STARTTLS PF-->>MC: 250 OK (TLS negotiated) MC-->>PF: AUTH LOGIN (SASL) PF-->>DV: verify credentials via SASL socket DV-->>PF: authenticated OK MC-->>PF: send message (DATA) PF-->>DK: pass to milter DK-->>PF: DKIM-Signature header added PF-->>RMX: SMTP delivery (TLS + signed) RMX-->>PF: 250 OK queued Note over MC,DV: Reading mail MC-->>DV: IMAP CONNECT :993 TLS DV-->>MC: folder list + messages

Challenges & Solutions

  • Mail landing in spam at Gmail: Initial deliveries were filtered as spam due to a missing PTR record. The VPS provider's reverse DNS panel was used to set the PTR for the server's IP to mail.tyfsadik.org, closing the authentication gap.
  • Postfix TLS certificate permission errors: Postfix could not read the Let's Encrypt private key because its process runs as the postfix user, which lacked read access to the /etc/letsencrypt/live/ directory. Resolved by adding the postfix user to the ssl-cert group and adjusting the cert directory permissions with chmod 750.
  • DKIM signing failing silently: Messages were sent without DKIM headers because the milter socket was not accessible to Postfix. Fixed by changing the milter socket from a Unix socket to a TCP socket (inet:12301@localhost) which is accessible regardless of socket file permissions.
  • Port 25 blocked by VPS provider: Many VPS providers block outbound port 25 by default to prevent spam. Resolved by submitting a ticket to the provider to unblock port 25 for the server IP after demonstrating legitimate email server configuration.

What I Learned

  • SMTP protocol flow: EHLO, STARTTLS, AUTH, DATA, and QUIT sequence
  • Email authentication stack: SPF authorization, DKIM cryptographic signing, DMARC policy enforcement
  • PTR record importance for SMTP reputation and inbox delivery
  • Postfix queue management, delivery logs, and bounce handling
  • Dovecot SASL integration with Postfix for submission authentication
  • TLS certificate lifecycle management for non-Nginx services using deploy hooks

How I built it

Running your own mail server is famously a deliverability project disguised as a server project. The goal here was full ownership of tyfsadik.org mail without landing in spam folders, so authentication and reputation got as much attention as the daemons.

  1. Phase 1, provision the platform: Deployed a Debian 12 VPS and pointed the tyfsadik.org DNS at it, including reverse DNS which receiving servers check.
  2. Phase 2, configure Postfix: Set up Postfix for SMTP on port 25 and authenticated submission on port 587 with mandatory STARTTLS, using Maildir storage.
  3. Phase 3, configure Dovecot: Added Dovecot for IMAP access with SASL authentication wired into Postfix submission.
  4. Phase 4, prove legitimacy with DNS: Published SPF, DKIM (via OpenDKIM signing), and DMARC records so receivers can verify every message is authorized.
  5. Phase 5, secure and connect clients: Issued Let's Encrypt certificates for SMTP and IMAP, then configured Thunderbird and mobile clients.

What you need

CategoryItemNotes
HardwareVPS with a static public IPPort 25 must be open; many providers block it by default, so confirm first
AccountsA domain with full DNS controlSPF, DKIM, and DMARC all live in DNS
AccountsReverse DNS (PTR) set by the VPS providerReceivers check that the IP resolves back to the mail hostname
SoftwareDebian 12, Postfix, Dovecot, OpenDKIM, CertbotThe full mail stack
SkillsDNS record management and SMTP fundamentalsAuthentication records are where deliverability is won or lost

Challenges and lessons learned

  • Deliverability to major providers: Gmail and Outlook are skeptical of new sending IPs. Lesson: warm up gradually, keep volume consistent, and monitor DMARC reports for failures.
  • DKIM key rotation: rotating keys means publishing new DNS records before switching signers. Lesson: run old and new selectors in parallel during the transition window.
  • Inbound spam: owning the server means owning the spam problem too. Lesson: plan for server-side filtering early rather than bolting it on after the first spam wave.

What is next

  • Deploy Rspamd for server-side spam and phishing filtering with per-user scores.
  • Add Sieve filtering so users get server-side rules that work across all clients.
  • Build queue health monitoring with alerts for deferred mail and blacklist hits.
  • Evaluate a lightweight webmail option for access from untrusted devices.
Postfix Dovecot DKIM Email Linux Privacy Self-Hosted