<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: khripkooleg</title>
    <description>The latest articles on DEV Community by khripkooleg (@khripkooleg).</description>
    <link>https://dev.to/khripkooleg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3768312%2F0dad2abf-858f-4d36-af7b-ce2c4404cbd0.jpg</url>
      <title>DEV Community: khripkooleg</title>
      <link>https://dev.to/khripkooleg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khripkooleg"/>
    <language>en</language>
    <item>
      <title>Let's build a Local Mail Server from Scratch with Postfix and Dovecot</title>
      <dc:creator>khripkooleg</dc:creator>
      <pubDate>Thu, 12 Feb 2026 12:20:10 +0000</pubDate>
      <link>https://dev.to/khripkooleg/lets-build-a-local-mail-server-from-scratch-with-postfix-and-dovecot-3gje</link>
      <guid>https://dev.to/khripkooleg/lets-build-a-local-mail-server-from-scratch-with-postfix-and-dovecot-3gje</guid>
      <description>&lt;p&gt;My boss at work decided it was time to test my knowledge, patience, and DevOps engineering skills, so he gave me a task that seemed simple at first glance but was actually guite challenging.&lt;/p&gt;

&lt;p&gt;We decided to build our own &lt;strong&gt;local mail server&lt;/strong&gt; from scratch for use among our colleagues. Instead of using ready-made Docker images, I created a mail server using &lt;strong&gt;Postfix&lt;/strong&gt; and &lt;strong&gt;Dovecot&lt;/strong&gt;, implemented virtual users, enabled LMTP delivery, and secured everything. &lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Before we begin...&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before we start creating our own mail server, we need to decide on the technologies we will use. &lt;/p&gt;

&lt;p&gt;Without a doubt, the core of our project are Postfix and Dovecot, which serve a single purpose - to help us create a mail server.&lt;br&gt;
I'll quickly explain what they do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Postfix&lt;/strong&gt; &lt;br&gt;
Postfix is responsible for handling SMTP traffic. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receives incoming email&lt;/li&gt;
&lt;li&gt;Sends outgoing email&lt;/li&gt;
&lt;li&gt;Decides whether mail should be relayed or delivered locally&lt;/li&gt;
&lt;li&gt;Enforces relay and security rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dovecot&lt;/strong&gt; &lt;br&gt;
Dovecot acts as the mail access server. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores emails ( Maildir format )&lt;/li&gt;
&lt;li&gt;Provides IMAP/POP3 access&lt;/li&gt;
&lt;li&gt;Handles user authentication&lt;/li&gt;
&lt;li&gt;Accepts local delivery via LMTP&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Mail Flow Design&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Before configuring the services, let's quickly understand how email would flow through the system.&lt;/p&gt;

&lt;p&gt;A mail server is essentially a pipeline. To debug more precisely we need to know how messages flow between components.&lt;/p&gt;

&lt;p&gt;In my setup, the mail flow looks like this:&lt;/p&gt;

&lt;p&gt;Client -&amp;gt; SMTP -&amp;gt; LMTP -&amp;gt; Dovecot -&amp;gt; Maildir&lt;br&gt;
Client -&amp;gt; IMAP -&amp;gt; Dovecot -&amp;gt; Maildir&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sending an Email&lt;/strong&gt;&lt;br&gt;
When user sends an email:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client connects to Postfix using SMTP ( port 587 ).&lt;/li&gt;
&lt;li&gt;Postfix validated authentication and checks relay restrictions.&lt;/li&gt;
&lt;li&gt;If the recipient is a local virtual user, Postfix forwards the message via LMTP to Dovecot.&lt;/li&gt;
&lt;li&gt;Dovecot stores the message in the user's Maildir directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of using the Postfix's built-in virtual delivery system, I chose LMTP because it allows Dovecot to handle the final delivery process directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading an Email&lt;/strong&gt;&lt;br&gt;
When a user checks their mailbox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client connects to Dovecot via IMAP.&lt;/li&gt;
&lt;li&gt;Dovecot authenticates the user using &lt;em&gt;passdb&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;After successful authentication, Dovecot retrieves emails from the Maildir storage.&lt;/li&gt;
&lt;li&gt;The client receives the message list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mail storage format Maildir&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Maildir&lt;/strong&gt; is a mail storage format provided by Dovecot to store messages on the server. &lt;br&gt;
In Maildir, each message is stored as an individual file inside a structured directory hierarchy ( &lt;code&gt;tmp/&lt;/code&gt;, &lt;code&gt;new/&lt;/code&gt;, and &lt;code&gt;cur/&lt;/code&gt; ).&lt;br&gt;
This design eliminated file locking issues common in older mailbox format and allows multiple processes to work safely in parallel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSL/TLS Encryption: Secure Communication&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;SSL (Secure Sockets Layer)&lt;/strong&gt; and &lt;strong&gt;TLS (Transport Layer Security)&lt;/strong&gt; are protocols that encrypt the communication between email servers and clients, protecting your emails and login credentials during transmission over the internet.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;Installing the Core Components&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once the architecture is clear, we can start our project. First, let's install the necessary components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt install postfix dovecot-imapd dovecot-lmtpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might ask: "Why virtual users?". Creating Linux system users for each email account would tightly couple mail accounts with operating system accounts.&lt;/p&gt;

&lt;p&gt;This approach has several drawbacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every email user = real system user.&lt;/li&gt;
&lt;li&gt;Potential shell access risks.&lt;/li&gt;
&lt;li&gt;Harder to scale and manage.&lt;/li&gt;
&lt;li&gt;Poor separation of concers.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Postfix configuration&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now that we have installed all the main components, we can move on to configuring Postfix.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/postfix/main.cf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basic Identity&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;myhostname = hostname.domain
mydomain = domain
myorigin = $mydomain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These parameters define how the server identifies itself when sending and receiving mail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network Configuration&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;inet_interfaces = all
inet_protocols = all
mynetworks = 127.0.0.0/8 ...
mydestination = localhost, localhost.$mydomain
relayhost = 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;inet_interfaces - ensures Postfix listens on all interfaces&lt;/li&gt;
&lt;li&gt;inet_protocols - IPv4 + IPv6&lt;/li&gt;
&lt;li&gt;mynetworks - defines which subnets are trusted and allowed to relay mail.&lt;/li&gt;
&lt;li&gt;mydestination - defines the domains that are trusted.&lt;/li&gt;
&lt;li&gt;relayhost ( empty ) - the server won't send any messages through another SMTP server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mailbox Settings&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mailbox_size_limit = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define that there is no restrictions on mailbox size.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relay Restrictions &amp;amp;&amp;amp; Recipient&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smtpd_recipient_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents the server from becoming an open relay.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reject_unauth_destination - is key option. It prohibits delivery to domain for which the server is not responsible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SASL Authentication&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It simply tells Postfix to not save the users and let the Dovecot do the job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLS&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smtpd_tls_cert_file = /etc/postfix/certs/server.crt
smtpd_tls_key_file = /etc/postfix/certs/server.key
smtpd_tls_security_level = may
smtp_tls_security_level = may
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We tell the server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;That we support TLS.&lt;/li&gt;
&lt;li&gt;TLS is supported but not required, and for production, encrypt is better.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the local environments is perfect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual Domain &amp;amp;&amp;amp; LMTP&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;virtual_mailbox_domains = domain
virtual_transport = lmtp:unix:private/dovecot-lmtp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;virtual_mailbox_domains - we tell Postfix which domain it should handle as virtual mailboxes. In other words, Postfix will accept mails for this domain and deliver them to virtual users.&lt;/li&gt;
&lt;li&gt;virtual_transport = lmtp:unix:private/dovecot-lmtp - we tell Postfix how to deliver mail for those virtual mailboxes. &lt;strong&gt;lmtp&lt;/strong&gt; is the &lt;strong&gt;Local Mail Transfer Protocol&lt;/strong&gt;, which is used to hand mails over to Dovecot for delivery. &lt;strong&gt;unix:private/dovecot-lmtp&lt;/strong&gt; specifies the &lt;strong&gt;socket (a kind of local connection) that Dovecot is listening on&lt;/strong&gt; to receive these emails. &lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Once Postfix is fully configured... wait! We are not done yet. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Postfix Ports&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After setting up Postfix, we need to make sure the mail server can actually receive connections. For that, we need to open the necessary ports in our &lt;strong&gt;firewall&lt;/strong&gt; (UFW) and configure &lt;strong&gt;master.cf&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;SMTP for receiving mail from other mail servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;587&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;SMTP submission for sending mail from email clients (authenticated)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;465&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;SMTP over SSL (optional, secure submission)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ufw allow 25/tcp
sudo ufw allow 587/tcp
sudo ufw allow 465/tcp
sudo ufw reload
sudo ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;master.cf&lt;/strong&gt; file defines how Postfix listens and handles connections.&lt;/p&gt;

&lt;p&gt;Submissions defines the outgoing from clients.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/postfix/master.cf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;submission inet n       -       y       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;strong&gt;-o&lt;/strong&gt; option overrides default Postfix behavior for this service only, like forcing TLS and enabling authentication.&lt;/p&gt;




&lt;p&gt;Now that we are finished configuring the Postfix, we can move on to configuring Dovecot.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Dovecot Configuration&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After Postfix is configured to handle sending and receiving mails, Dovecot takes care of user authentication and mail storage access. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create the vmail User&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;All virtual mailboxes will be stored under a dedicated system user called &lt;strong&gt;vmail&lt;/strong&gt;. This avoids creating a system account for each email user, improving security and manageability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo groupadd -g 5000 vmail
sudo useradd -g 5000 -u 5000 vmail -s /bin/bash -m /home/vmail
sudo passwd vmail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;-g 5000 - sets the primary group to vmail&lt;/li&gt;
&lt;li&gt;-u 5000 - assigns a UID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Create a Virtual User Password File&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dovecot needs a lsit of virtual users and their passwords. This can be a simple text file for local testing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/dovecot/passwd&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sender@domain:{PLAIN}passwd:5000:5000::/home/vmail/domain/sender
receiver@domain:{PLAIN}passwd:5000:5000::/home/vmail/domain/receiver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Dovecot, virtual users are not real Linux users, but they still need file system access to read/write their mailboxes. Virtual usera are mapped to vmail's UID/GID so Dovecot processes access the files as &lt;strong&gt;vmail&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sender@domain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Virtual email address (login name).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;{PLAIN}passwd&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Password for this user (&lt;code&gt;{PLAIN}&lt;/code&gt; = plain text for testing).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;5000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UID of the system user Dovecot should use (&lt;code&gt;vmail&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;5000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GID of the system group (&lt;code&gt;vmail&lt;/code&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&lt;/code&gt; (empty)&lt;/td&gt;
&lt;td&gt;Typically used for extra info like quota; empty here.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/home/vmail/domain/sender&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Home directory / Maildir path for this user. Dovecot stores the mail here.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Configure Password and User Databases&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/dovecot/conf.d/auth-passwdfile.conf.ext&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;passdb passwd-file {
        driver = passwd-file
        auth_username_format = %{user}
        passwd_file_path = /etc/dovecot/passwd
}

userdb static-users {
        driver = static
        fields {
                uid = 5000
                gid = 5000
                home = /home/vmail/domain/%{user | username}
                mail = maildir:~/Maildir
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file tells Dovecot &lt;strong&gt;how to find virtual users and which system account should own their mail files&lt;/strong&gt;. There are two main sections: &lt;strong&gt;passdb&lt;/strong&gt; and &lt;strong&gt;userdb&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;passdb passwd-file - Password Database. Dovecot checks this when a user logs in.&lt;/li&gt;
&lt;li&gt;driver passwd-file - Dovecot reads users/passwords from a plain text file.&lt;/li&gt;
&lt;li&gt;auth_username_format - This ensured that Dovecot uses the exact username entered by the client ( before @ or with full mail depending on the option ).&lt;/li&gt;
&lt;li&gt;&lt;p&gt;passwd_file_path - Location of the file with virtual users and passwords.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;userdb static-users - This is a user database named static-users. It tells Dovecot who owns the mailbox.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;driver = static - Same system UID/GID for all virtual users.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;uid = 5000 - The system user ID used to acces mailbox files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;gid = 5000 - The system group ID used to access mailbox files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;home - The home path of a virtual user&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;mail - Location of the user's Maildir inside the home directory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mail Storage Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/dovecot/conf.d/10-mail.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mail_driver = maildir
mail_home = /home/vmail/domain/%{user | username}
mail_path = ~/Maildir
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where we tell Dovecot where and how to store messages for each virtual user.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mail_driver = maildir - Dovecot will use the Maildir format for storing emails.&lt;/li&gt;
&lt;li&gt;mail_home - This is the home directory for the virtual user's mailbox. &lt;code&gt;%{user | username}&lt;/code&gt; takes the local part of the email ( everything before @ )&lt;/li&gt;
&lt;li&gt;mail_path - This is where Dovecot actually reads and writes the user's emails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Authentication Settings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/dovecot/conf.d/10-auth.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth_mechanisms = plain login
auth_allow_cleartext = no

#!include auth-system.conf.ext
!include auth-passwdfile.conf.ext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file controls how users log in and which authentication backends Dovecot uses.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;auth_mechanisms - Specifies which authentication methods are allowed. &lt;strong&gt;plain&lt;/strong&gt; - username and password are sent as plain text. &lt;strong&gt;login&lt;/strong&gt; - a common method used by email clients.&lt;/li&gt;
&lt;li&gt;auth_allow_cleartext - Prevents sending passwords in plain text over unecrypted connections.&lt;/li&gt;
&lt;li&gt;#!include auth-system.conf.ext - We comment out his line so Dovecot will not use system Linux users for authentication, because we only want virtual users.&lt;/li&gt;
&lt;li&gt;!include auth-passwdfile.conf.ext - Includes the configuration file we created for virtual users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Master Process &amp;amp;&amp;amp; Socket Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/dovecot/conf.d/10-master.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service imap-login {
  inet_listener imap {
    port = 143
  }
}

service auth {
        unix_listener /var/spool/postfix/private/auth {
                mode = 0660
                user = postfix
                group = postfix
        }
        unix_listener auth-userdb {
                mode = 0660
                user = vmail
                group = vmail
        }
}

service lmtp {
        unix_listener /var/spool/postfix/private/dovecot-lmtp {
                mode = 0600
                user = postfix
                group = postfix
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file controls how Dovecot runs its services ( IMAP, LMTP, authentication ) and how they communicate with other processes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;service imap-login - Handles client logins via IMAP by setting up a TCP socket for standard IMAP port 143.&lt;/li&gt;
&lt;li&gt;service auth - Handles all user authentication requests by creating a &lt;strong&gt;UNIX socket&lt;/strong&gt; for Postfix. &lt;/li&gt;
&lt;li&gt;service lmtp - Handles mail delivery from Postfix by creating a UNIX socket. &lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Socket/Port&lt;/th&gt;
&lt;th&gt;User/Group&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;imap-login&lt;/td&gt;
&lt;td&gt;IMAP client connections&lt;/td&gt;
&lt;td&gt;TCP 143&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Allows clients to read mail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auth&lt;/td&gt;
&lt;td&gt;User authentication&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/var/spool/postfix/private/auth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;postfix:postfix&lt;/td&gt;
&lt;td&gt;Postfix can check credentials&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auth-userdb&lt;/td&gt;
&lt;td&gt;User info query&lt;/td&gt;
&lt;td&gt;internal&lt;/td&gt;
&lt;td&gt;vmail:vmail&lt;/td&gt;
&lt;td&gt;Dovecot internal use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lmtp&lt;/td&gt;
&lt;td&gt;Mail delivery from Postfix&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/var/spool/postfix/private/dovecot-lmtp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;postfix:postfix&lt;/td&gt;
&lt;td&gt;Delivers mail to Maildir&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;LMTP Protocol Settings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/dovecot/conf.d/20-lmtp.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protocol lmtp {
  auth_username_format = %{user}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file controls how Dovecot handles mail delivery via LMTP from Postfix.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;protocol lmtp - Tells Dovecot we are configuring the LMTP protocol.&lt;/li&gt;
&lt;li&gt;auth_username_format - Ensures Dovecot uses the exact username sent by Postfix when delivering mail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, we should enable LMTP in Dovecot config file.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/dovecot/dovecot.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protocols = imap lmtp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SSL/TLS Settings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/dovecot/conf.d/10-ssl.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssl = required
ssl_server_cert_file = /etc/postfix/certs/server.crt
ssl_server_key_file = /etc/postfix/certs/server.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These settings make sure that all client connections to Dovecot are encrypted, keeping passwords and emails secure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ssl = required - Forces all connections to use SSL/TLS.&lt;/li&gt;
&lt;li&gt;ssl_server_cert/key_file - Specifies the certificate and private key Dovecot uses for TLS encryption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;! Since we are creating a local mail server, we won't need this, as we won't be trying to send messages to external domains such as gmail.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Firewall Settings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's allow 143 port for IMAP / POP3 protocols.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ufw allow 143/tcp
sudo ufw reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Even though our mail server is local, I'm still creating is inside my corporate network and email clients and Postfix still rely on DNS to resolve domain names.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Postfix needs to know where to send mail ( MX records ) even if it's just internal.&lt;/li&gt;
&lt;li&gt;Clients need to resolve the domain to my mail server's IP.&lt;/li&gt;
&lt;li&gt;Forward zones help our DNS server map our domain to our mail server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;DNS Zones Set Up&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Forward Zone&lt;/strong&gt; maps domain names to IP addresses.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zone file: Contains all the records for your domain.&lt;/li&gt;
&lt;li&gt;Forwarding: Internal clients query your DNS for domain.com. If the DNS knows about the zone, it replies; otherwise, it can forward requests to external DNS.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Records: The forward zone contains different types of records.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A RECORD&lt;/strong&gt; - Maps a hostname to an IP address.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ANAME&lt;/strong&gt; - Some DNS server support aliases as a &lt;strong&gt;CNAME&lt;/strong&gt; replacement at the root domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CNAME&lt;/strong&gt; - Creates an alias from one hostname to another.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MX Record&lt;/strong&gt; - Directs email to the mail server for our domain.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Putting It All Together&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Record → points mail.domain.com → 192.168.x.x.&lt;/li&gt;
&lt;li&gt;CNAME (optional) → smtp.domain.com → mail.domain.com.&lt;/li&gt;
&lt;li&gt;ANAME / ALIAS (optional) → domain.com → mail.domain.com.&lt;/li&gt;
&lt;li&gt;MX Record → domain.com → mail.domain.com with priority 10.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Monitoring logs is crucial for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checking mail delivery process.&lt;/li&gt;
&lt;li&gt;Detecting authentication or TLS issues.&lt;/li&gt;
&lt;li&gt;Debugging error during SMTP, LMTP, or IMAP operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Logging Mail Server&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Postfix logs are usually written to syslog.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo tail -f /var/log/mail.log&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What to look for:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Log Entry&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status=sent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Email was successfully delivered.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status=bounced&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delivery failed (check recipient or MX).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reject_unauth_destination&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Someone tried to relay through your server — Postfix blocked it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sasl_method=PLAIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Authentication attempt logged.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To see Dovecot logs authentication and mail access we can use &lt;code&gt;journalctl -u dovecot&lt;/code&gt;. In order to extend the logs we should edit the &lt;code&gt;/etc/dovecot/conf.d/10-logging.conf&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth_debug_passwords = yes
log_debug=category=mail
mail_plugins {
        notify = yes
        mail_log = yes
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;auth_debug_password - Shows logs in case of password mismatches.&lt;/li&gt;
&lt;li&gt;log_debug=category=mail - Shows mail process debugging, this helps to figure out why Dovecot isn't finding mails.&lt;/li&gt;
&lt;li&gt;mail_plugins - Provides more event logging for mail processes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To check if ports are listening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ss -tulpn | grep -E '25|587|456|143'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Testing Local Mail Server&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now that Postfix and Dovecot are configured, it's crucial to verify that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SMTP works.&lt;/li&gt;
&lt;li&gt;LMTP delivery to Maildir works.&lt;/li&gt;
&lt;li&gt;IMAP works.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can do this easily with &lt;strong&gt;Telnet&lt;/strong&gt; ( or OpenSSL ).&lt;/p&gt;

&lt;p&gt;First thing first, restart your services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl restart postfix
sudo systemctl restart dovecot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test SMTP ( Port 587 / 25 )&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;telnet domain 587
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a response like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;220 domain ESMTP Postfix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now send a test email manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EHLO domain.com
AUTH LOGIN
MAIL FROM:&amp;lt;sender@domain&amp;gt;
RCPT TO:&amp;lt;receiver@domain&amp;gt;
DATA
Subject: Test Email
Hello! This is a test.
.
QUIT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test IMAP ( Port 143 )&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;telnet domain 143
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* OK Dovecot ready.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now check an inbox.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a login sender@domain.com passwd
b select inbox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;You now have a fully functional local mail server that is capable of handling virtual users, sending and receiving emails, and securely storing messages in Maildir format! &lt;/p&gt;

&lt;p&gt;If you have any questions or suggestions, I will be happy to hear them! Criticism is also welcome!&lt;/p&gt;

</description>
      <category>postfix</category>
      <category>dovecot</category>
      <category>devops</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
