User Tools

Site Tools


computing:mailserver-trixie

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
computing:mailserver-trixie [2025/11/05 04:18] oemb1905computing:mailserver-trixie [2026/08/31 00:07] (current) oemb1905
Line 10: Line 10:
  
 ------------------------------------------- -------------------------------------------
 +
 +  * [[https://cloud.haacksnetworking.org/index.php/s/P255xsGX37We4BS|Slides]]
 +  * [[https://content.haacksnetworking.org/w/erLgeVDEdeUf84FXq2Xk4f|SeaGL Presentation]]
 +  * [[https://tech.haacksnetworking.org/2025/06/10/your-email-your-rules-self-hosting-simplified/|Blog Post]]
  
 ====== Introduction ====== ====== Introduction ======
 ~~NOTOC~~ ~~NOTOC~~
  
-Contrary to popular belief, it's entirely possible to self-host email servers. Like others, I listened to the propaganda that "it's no longer feasible to self-host email" or "it's too complex and servers won't respect your mail health anyway" or other such explanations. In 2014, while running workshops for students on security and networking, one of my student'parent (a Ruby dev) said he agreed with me and that as far as he knew, postfix was fairly straightforward. From that day forward, I decided to approach self-hosting email servers the same way as I approach self-hosting any instance (Nextcloud, Dokuwiki, Airsonic, Gitlab, etc.). I decided that it must be entirely possible and that it was merely a question of how. So, from 2014 - 2018, from Wheezy to Buster, I began using my spare hacking time to create my first smtp relay in 2015 and later setup my first proper email server in 2018. As it turns out, it is relatively straight forward to setup a functional base server. Getting the ecosystem to respect your email, however, takes a little tender care.+Contrary to popular belief, it's entirely possible to self-host email servers. Like others, I listened to the propaganda that "it's no longer feasible to self-host email" or "it's too complex and servers won't respect your mail health anyway" or other such explanations. In 2014, while running workshops for students on security and networking, one of my student'parents (a Ruby dev) said he agreed with me and that as far as he knew, postfix was fairly straightforward. From that day forward, I decided to approach self-hosting email servers the same way as I approach self-hosting any instance ([[https://nextcloud.haacksnetworking.org|Nextcloud]][[https://music.outsidebox.club|Airsonic]][[https://repo.haacksnetworking.org|Gitlab]], etc.). I decided that it must be entirely possible and that it was merely a question of how. So, from 2014 - 2018, from Wheezy to Buster, I began using my spare hacking time to create my first smtp relay in 2015 and later setup my first proper email server in 2018. As it turns out, it is relatively straight forward to setup a functional base server. Getting the ecosystem to respect your email, however, takes a little tender care. It's entirely doable though: [[https://mail.haacksnetworking.org|My Business Webmail]]. 
 + 
 +I did not migrate my personal emails and/or business infrastructure until 2021. During 2018 - 2021, I would intermittently test, identify and fix failures, breakages, and read up more on DNS records and worked to gain a deeper understanding of the ecosystem. I spent countless hobbyist hours reviewing forums, Stack Exchange, and, of course, Linux Babe. I also shared notes and perspectives with a local colleague and fellow IT/networking professional, [[http://schaeferconsulting.com|Schaefer Consulting]]. I was not in any rush to migrate, and I also wanted to develop a system that balanced complexity with reliability/convenience. After initially developing a [[https://wiki.haacksnetworking.org/doku.php?id=computing:exim4|mail relay recipe]] both under/alongside Schaefer and on my own, I ultimately decided to switch to postfix for incoming/outgoing, or what I call a proper email server (not merely a relay or send-only MTA). This was pure chance, namely, as the first server recipe I got working for IMAP/dovecot was on my postfix VPS not the exim4 VPS, so I simply got motivated to keep fixing it until it all worked. Until that time, circa 2018, I was tinkering back and forth on two different VPSs, one with exim4 and another with postfix, testing different strategies. To this day, I continue to use exim4 for relaying email from hosts behind NAT. For proper email servers, I currently use postfix. In getting everything to work, my goal was to only increase complexity if/when it was required for proper functioning. For this reason, I chose to use simple UNIX users.
  
-I did not migrate my personal emails and/or business infrastructure until 2021. During 2018 2021, I would intermittently test, identify and fix failures, breakages, and read up more on DNS records and worked to gain a deeper understanding of the ecosystem. I spent countless hobbyist hours reviewing forums, Stack Exchange, and, of course, Linux Babe. I also shared notes and perspectives with a local colleague and fellow IT/networking professional, Schaefer Consulting. I was not in any rush to migrate, and I also wanted to develop a system that balanced complexity with reliability/convenience. After initially developing a mail relay recipe both under/alongside Schaefer and on my own, I ultimately decided to switch to postfix for incoming/outgoing, or what I call a proper email server (not merely a relay or send-only MTA). This was pure chance, namely, as the first server recipe I got working for IMAP/dovecot was on my postfix VPS not the exim4 VPS, so I simply got motivated to keep fixing it until it all worked. Until that time, circa 2018, I was tinkering back and forth on two different VPSs, one with exim4 and another with postfix, testing different strategies. To this day, I continue to use exim4 for relaying email from hosts behind NAT. For proper email servers, I currently use postfix. In getting everything to work, my goal was to only increase complexity if/when it was required for proper functioning. For this reason, I chose to use simple UNIX users.+{{ :computing:screenshot_from_2025-11-04_20-48-13.png?direct&800 |}}
  
-{{ :computing:screenshot_from_2025-11-04_20-48-13.png?direct&600 |}}+Results[[https://www.mail-tester.com/test-8hia4koy2|Mail Tester Results]]
  
-In my case, the servers I built are on VMs that reside on a custom virtualization stack stack that uses virsh and kvm/qemu on a Debian SuperMicro server (Xeon Silvers) that I co-locate at Brown Rice data center. My virtualization stack has roughly 30 VMs at present, with the host boasting over 500 virtual cores and 384GB of RAM. My primary business email server, for haacksnetworking.org, is an 8-core Virtual Machine with 8GB of RAM that resides on that same SuperMicro. Remember, though, as long as your VPS host provides PTR (reverse DNS) access, you can do this on a very basic ($5-$10 per month) VPS. My Taos server is also the proud host of the following services:+In my case, the servers I built are on VMs that reside on a [[https://wiki.haacksnetworking.org/doku.php?id=computing:vmserver|custom virtualization stack]] that uses virsh and kvm/qemu on a Debian SuperMicro server (Xeon Silvers) that I co-locate at Brown Rice data center. My virtualization stack has roughly 30 VMs at present, with the host boasting over 500 virtual cores and 384GB of RAM. My primary business email server, for haacksnetworking.org, is an 8-core Virtual Machine with 8GB of RAM that resides on that same SuperMicro. Remember, though, as long as your VPS host provides PTR (reverse DNS) access, you can do this on a very basic ($5-$10 per month) VPS. My Taos server is also the proud host of the following services:
  
-  * Mastodon: gnulinux.social +  * **Mastodon**: [[https://gnulinux.social|GNU/Linux Social]] 
-  * Matrix: gnulinux.club +  * **Matrix**: [[https://gnulinux.club|GNU/Linux Club]] 
-  * PeerTube: gnulinux.tube +  * **PeerTube**: [[https://gnulinux.tube|GNU/Linux Tube]] 
-  * Navidrome: gnulinux.studio +  * **Navidrome**: [[https://gnulinux.studio|GNU/Linux Studio]] 
-  * Pixelfed: (forthcoming)+  * **Pixelfed**: (forthcoming)
  
-My work on email servers and these public instances is all free of charge and shared under Creative Commons Attribution, Share-Alike, Non-Commercial. If you like the tutorials and/or services I provide, please consider giving back at Libera Pay. Alright, let's get this email server set up!+This particular tutorial is [[https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en|CC BY-NC-SA 4.0]] instead of FDL 1.3 (like the majority of posts on this Wiki). If you like the tutorials and/or services I provide, please consider giving back at [[https://liberapay.com/oemb1905/|Libera Pay]]. Alright, let's get this email server set up!
  
 ====== Part I - DNS Records ====== ====== Part I - DNS Records ======
Line 57: Line 63:
   SPF (mail.domain.tld): v=spf1 a mx ip4:8.28.86.125 ip6:2a10:e780:10:28::2 ~all   SPF (mail.domain.tld): v=spf1 a mx ip4:8.28.86.125 ip6:2a10:e780:10:28::2 ~all
  
-The SPF record, or sender policy framework, instructs mail servers about which IPs are permitted to send email from a given domain. I include the a flag for redundancy and or for safety. Allowing any A-record in the domain to send using this domain is very permissive and also helps in case you misconfigure an explicit record while testing. The mx flag ensures that email sent from my own MX servers is explicitly permitted. Although not essentially required to include a or mx if your server's IP is specified, I find it helpful to provide extra resilience for cases where you change or scale. For example, if I decide to add a backup mail server, the mx flag automatically approves it, i.e., I don't need to change the record each time. If I migrate to a different node and/or IP, the A specification allows sending immediately and I can edit the individual TXT records later. So, specifying all three together (A, MX, and IPs) is beneficial for updating, changing, or scaling up your email offering. Additionally, bounces are often sent from the mx server, not the outbound IP, which could potentially differ from the IP of the primary MTA. Some email servers, moreover, compare spf IP to sender domain, so including a and mx alongside the IPs provides full alignment and therefore better email health. As for the ~all, I use that because it specifies "soft fail" if an origin IP does not align. This is important because mistakes happen and sysadmins don't want phantom rejections. If you instead set this flag to -all (reject aka hard fail), then recipients won't receive emails if you configured an IP or DNS record in error. Conversely, setting the flag to +all defeats the purpose of the record by permitting everything. The soft fail flag ~all allows sysadmins to identify misconfigurations and fix them, while ensuring that no emails are auto-rejected by the recipient due to your own spf policy. Of course, after sufficient testing, you can optionally change this flag to hard fail, but proceed with caution. The pct flag controls what portion of emails should be evaluated by dmarc, which I keep at 100%. By specifying 100%, you are stating that all your emails should be checked. In thus testing the whole population (instead of a random sample) of your emails, you increase your own chances of failing merely by increasing the number of evaluated emails. However, it is important to remember that mail servers rarely reject incoming email due to dmarc failure alone (almost never), so just because you increase dmarc failures, those won't result in rejections per se. As long as all the other records are solid, I prefer to error on the side of getting more reports and every email evaluated, rather than permitting phantom allowances.+The SPF record, or sender policy framework, instructs mail servers about which IPs are permitted to send email from a given domain. I include the a flag for redundancy and or for safety. Allowing any ''A'' record in the domain to send using this domain is very permissive and also helps in case you misconfigure an explicit record while testing. The mx flag ensures that email sent from my own MX servers is explicitly permitted. Although not essentially required to include ''a'' or ''mx'' if your server's IP is specified, I find it helpful to provide extra resilience for cases where you change or scale. For example, if I decide to add a backup mail server, the mx flag automatically approves it, i.e., I don't need to change the record each time. If I migrate to a different node and/or IP, the A specification allows sending immediately and I can edit the individual TXT records later. So, specifying all three together (''A''''MX'', and IPs) is beneficial for updating, changing, or scaling up your email offering. Additionally, bounces are often sent from the ''mx'' server, not the outbound IP, which could potentially differ from the IP of the primary MTA. Some email servers, moreover, compare spf IP to sender domain, so including a and mx alongside the IPs provides full alignment and therefore better email health. As for the ''~all'', I use that because it specifies "soft fail" if an origin IP does not align. This is important because mistakes happen and sysadmins don't want phantom rejections. If you instead set this flag to ''-all'' (reject aka hard fail), then recipients won't receive emails if you configured an IP or DNS record in error. Conversely, setting the flag to ''+all'' defeats the purpose of the record by permitting everything. The soft fail flag ''~all'' allows sysadmins to identify misconfigurations and fix them, while ensuring that no emails are auto-rejected by the recipient due to your own spf policy. Of course, after sufficient testing, you can optionally change this flag to hard fail, but proceed with caution. The ''pct'' flag controls what portion of emails should be evaluated by ''dmarc'', which I keep at 100%. By specifying 100%, ''pct=100'', you are stating that all your emails should be checked. In thus testing the whole population (instead of a random sample) of your emails, you increase your own chances of failing merely by increasing the number of evaluated emails. However, it is important to remember that mail servers rarely reject incoming email due to dmarc failure alone (almost never), so just because you increase dmarc failures, those won't result in rejections per se. As long as all the other records are solid, I prefer to error on the side of getting more reports and every email evaluated, rather than permitting phantom allowances.
  
 ====== Part II - Installing Postfix+Dovecot, Setting up LAMP ====== ====== Part II - Installing Postfix+Dovecot, Setting up LAMP ======
Line 63: Line 69:
 The first part of the tutorial, i.e., setting up DNS records, is now complete. It's now time to address installation and setup of postfix+dovecot. Do not proceed to this part of the tutorial unless you completed the DNS steps above. Put simply, postfix+dovecot both require proper DNS resolution to work. The only exception to this rule is for our DKIM record, which requires us first configuring the server before we cut the keypair and create the associated TXT record. Other than that, make sure DNS is ready to go before proceeding. Okay, let's ssh into the VM/VPS and do the following: The first part of the tutorial, i.e., setting up DNS records, is now complete. It's now time to address installation and setup of postfix+dovecot. Do not proceed to this part of the tutorial unless you completed the DNS steps above. Put simply, postfix+dovecot both require proper DNS resolution to work. The only exception to this rule is for our DKIM record, which requires us first configuring the server before we cut the keypair and create the associated TXT record. Other than that, make sure DNS is ready to go before proceeding. Okay, let's ssh into the VM/VPS and do the following:
  
-  sudo apt update && sudo apt upgrade -y\ +  sudo apt update && sudo apt upgrade -y 
-  sudo apt install mailutils postfix ufw fail2ban nginx apache2 php8.4-fpm php8.4-mysql php8.4-curl php8.4-gd php8.4-mbstring php8.4-xml php8.4-zip dovecot-core dovecot-imapd dovecot-lmtpd+  sudo apt install mailutils postfix ufw fail2ban apache2 php8.4-fpm php8.4-mysql php8.4-curl php8.4-gd php8.4-mbstring php8.4-xml php8.4-zip dovecot-core dovecot-imapd dovecot-lmtpd
  
 It's also important that that the host knows how to identify itself properly. Let's open it up ''/etc/hosts'' and make it is setup correctly. It's also important that that the host knows how to identify itself properly. Let's open it up ''/etc/hosts'' and make it is setup correctly.
  
-<code bash>+<code>
 127.0.0.1 localhost 127.0.0.1 localhost
 127.0.1.1 mail.haacksnetworking.org haacksnetworking 127.0.1.1 mail.haacksnetworking.org haacksnetworking
Line 101: Line 107:
 {{ :computing:postfix7.png?direct&600 |}} {{ :computing:postfix7.png?direct&600 |}}
  
-For the mail name, put haacksnetworking.org or domain.com. Leave most fields at default values; make sure other destinations populated correctly. Do not select All unless you have properly configured records and interfaces for both. Only select and specify what you have records for, otherwise they will fail if they hop to the unsupported protocol. I speak from direct experience. +For the mail name, put ''haacksnetworking.org'' or ''domain.com''. Leave most fields at default values; make sure other destinations populated correctly. Do not select All unless you have properly configured records and interfaces for both. Only select and specify what you have records for, otherwise they will fail if they hop to the unsupported protocol. I speak from direct experience. Now that a basic postfix setup is in place, you can optionally install a firewall. If you choose to do this, I recommend the Uncomplicated Firewall, or ufw. Install it and open up all the ports required by postfix+dovecot and no others.
- +
-Now that a basic postfix setup is in place, you can optionally install a firewall. If you choose to do this, I recommend the Uncomplicated Firewall, or ufw. Install it and open up all the ports required by postfix+dovecot and no others.+
  
   sudo apt install ufw   sudo apt install ufw
Line 127: Line 131:
 Make sure to create ''vhost-mail.conf'' and enable it with ''a2ensite'' for the ''mail.haacksnetworking.org'' or ''mail.domain.com'' and run the same ''certbot'' command again for Let's Encrypt to create a cert for this subdomain as well. You need to specify and create a different webroot for ''ServerName'', ''ServerAdmin'', and ''DocumentRoot'' this subdomain, just like the parent domain. Don't run the certbot commands on both domains together as it might interpret that command as being wildcards and/or nested certificates (e.g., multi-site WordPress). When you installed postfix, it automatically populated the global config file over in ''/etc/postfix/main.cf'' with the specified choices. It's now time to configure individual services for postfix (smtpd, submission, etc.), so open up ''/etc/postfix/master.cf'' and add these blocks where appropriate. You can view an actual config of mine in the link below this block. Make sure to create ''vhost-mail.conf'' and enable it with ''a2ensite'' for the ''mail.haacksnetworking.org'' or ''mail.domain.com'' and run the same ''certbot'' command again for Let's Encrypt to create a cert for this subdomain as well. You need to specify and create a different webroot for ''ServerName'', ''ServerAdmin'', and ''DocumentRoot'' this subdomain, just like the parent domain. Don't run the certbot commands on both domains together as it might interpret that command as being wildcards and/or nested certificates (e.g., multi-site WordPress). When you installed postfix, it automatically populated the global config file over in ''/etc/postfix/main.cf'' with the specified choices. It's now time to configure individual services for postfix (smtpd, submission, etc.), so open up ''/etc/postfix/master.cf'' and add these blocks where appropriate. You can view an actual config of mine in the link below this block.
  
-<code bash>+<code>
 submission inet n - y - - smtpd submission inet n - y - - smtpd
   -o syslog_name=postfix/submission   -o syslog_name=postfix/submission
Line 151: Line 155:
 The changes above add service definitions and configurations for smtps (465) and submission (587), both of which handle mail submission, or the sending of email. Examples of these configurations can be found in ''/usr/share/doc/postfix/examples/''. Submission is the newer protocol, but I retain smtps for compatibility with older clients. Once that's done, it's time to edit ''/etc/postfix/main.cf'' to account for the TLS certificate we just cut with Let's Encrypt a few steps earlier. We can additionally specify for postfix to use auth mechanisms that leverage this cert and are consistent with the services setup in master.cf. Let's open ''/etc/postfix/main.cf'' and review/add/edit the following: The changes above add service definitions and configurations for smtps (465) and submission (587), both of which handle mail submission, or the sending of email. Examples of these configurations can be found in ''/usr/share/doc/postfix/examples/''. Submission is the newer protocol, but I retain smtps for compatibility with older clients. Once that's done, it's time to edit ''/etc/postfix/main.cf'' to account for the TLS certificate we just cut with Let's Encrypt a few steps earlier. We can additionally specify for postfix to use auth mechanisms that leverage this cert and are consistent with the services setup in master.cf. Let's open ''/etc/postfix/main.cf'' and review/add/edit the following:
  
-<code bash>+<code>
 # A) Leave the following upper-block defaults # A) Leave the following upper-block defaults
 smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
Line 163: Line 167:
 #smtpd_tls_security_level=may #smtpd_tls_security_level=may
 #smtp_tls_CApath=/etc/ssl/certs #smtp_tls_CApath=/etc/ssl/certs
-smtp_tls_security_level=may+#smtp_tls_security_level=may
 #smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache #smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
  
Line 169: Line 173:
 smtpd_tls_cert_file=/etc/letsencrypt/live/mail.haacksnetworking.org/fullchain.pem smtpd_tls_cert_file=/etc/letsencrypt/live/mail.haacksnetworking.org/fullchain.pem
 smtpd_tls_key_file=/etc/letsencrypt/live/mail.haacksnetworking.org/privkey.pem smtpd_tls_key_file=/etc/letsencrypt/live/mail.haacksnetworking.org/privkey.pem
-smtpd_tls_security_level=may+smtpd_tls_security_level = may
 smtpd_tls_loglevel = 1 smtpd_tls_loglevel = 1
 smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
Line 185: Line 189:
  
 # F) Primary Block; additional services go below or in dedicated config # F) Primary Block; additional services go below or in dedicated config
-smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination+smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination
 myhostname = mail.haacksnetworking.org myhostname = mail.haacksnetworking.org
 alias_maps = hash:/etc/aliases alias_maps = hash:/etc/aliases
Line 198: Line 202:
 inet_protocols = all inet_protocols = all
 message_size_limit = 52428800 message_size_limit = 52428800
 +cyrus_sasl_config_path = /etc/postfix/sasl #now default since early 2026
 </code> </code>
  
Line 208: Line 213:
 In order for dovecot to work, we need to specify which protocols we allow, the mail directory location, ensure that dovecot is a member of the mail group, and enable lmtp the for delivery agent. Lmtp is a local delivery agent and hands off processed incoming emails to the storage directories without requiring external authentication. This ensures everything is delivered locally, or within the hardened host. Larger distributed email systems require smtp for network hand offs between servers, but that's not required or helpful for mail server self-hosters. Open up ''/etc/dovecot/dovecot.conf'' and specify the protocols. It is also required to specfify the storage and config versions (updated for Trixie): In order for dovecot to work, we need to specify which protocols we allow, the mail directory location, ensure that dovecot is a member of the mail group, and enable lmtp the for delivery agent. Lmtp is a local delivery agent and hands off processed incoming emails to the storage directories without requiring external authentication. This ensures everything is delivered locally, or within the hardened host. Larger distributed email systems require smtp for network hand offs between servers, but that's not required or helpful for mail server self-hosters. Open up ''/etc/dovecot/dovecot.conf'' and specify the protocols. It is also required to specfify the storage and config versions (updated for Trixie):
  
-<code bash+<code> 
-protocols = imap lmtpdovecot_storage_version = 2.4.1 +protocols = imap lmtp 
-dovecot_config_version = 2.4+dovecot_storage_version = 2.4.1 
 +dovecot_config_version = 2.4.1
 # If you upgraded from Bookworm, comment out dictionary # If you upgraded from Bookworm, comment out dictionary
 # dict { # dict {
Line 220: Line 226:
  
  
-<code bash+<code> 
-mail_driver = maildirmail_path = ~/Maildirmail_inbox_path = ~/Maildir/.INBOX+mail_driver = maildir 
 +mail_path = ~/Maildir 
 +mail_inbox_path = ~/Maildir/.INBOX
 </code> </code>
  
 Make sure that the dovecot user and the simple UNIX users are both part of the mail group. Remember, this tutorial does not use virtual users and/or an associated database. This tutorial uses simple UNIX user names which means the yourusername listed below will have the email ''yourusername@haacksnetworking.org'', or ''yourusername@domain.com''. Make sure that the dovecot user and the simple UNIX users are both part of the mail group. Remember, this tutorial does not use virtual users and/or an associated database. This tutorial uses simple UNIX user names which means the yourusername listed below will have the email ''yourusername@haacksnetworking.org'', or ''yourusername@domain.com''.
  
-  sudo adduser yourusernamesudo adduser dovecot mailsudo adduser yourusername mail+  sudo adduser yourusername 
 +  sudo adduser dovecot mail 
 +  sudo adduser yourusername mail
  
 It is required that lmtp be configured for use with dovecot. To do that, edit ''/etc/dovecot/conf.d/10-master.conf'': It is required that lmtp be configured for use with dovecot. To do that, edit ''/etc/dovecot/conf.d/10-master.conf'':
  
-<code bash>+<code>
 service lmtp { service lmtp {
   unix_listener /var/spool/postfix/private/dovecot-lmtp {   unix_listener /var/spool/postfix/private/dovecot-lmtp {
Line 242: Line 252:
 Postfix also needs to know the delivery agent that's being used, otherwise it won't know what service to which it should send incoming email. In short, lmtp hands off emails from postfix's mail queue to dovecot's mail storage. This is done via a socket, so that needs to be configured in the main postfix configuration. Open up ''/etc/postfix/main.cf'' and define the socket as follows. It is recommended that you also disable UTF-8 support in headers and addresses so that legacy mail servers can handle emails from your server. Postfix also needs to know the delivery agent that's being used, otherwise it won't know what service to which it should send incoming email. In short, lmtp hands off emails from postfix's mail queue to dovecot's mail storage. This is done via a socket, so that needs to be configured in the main postfix configuration. Open up ''/etc/postfix/main.cf'' and define the socket as follows. It is recommended that you also disable UTF-8 support in headers and addresses so that legacy mail servers can handle emails from your server.
  
-<code bash+<code> 
-mailbox_transport = lmtp:unix:private/dovecot-lmtpsmtputf8_enable = no+mailbox_transport = lmtp:unix:private/dovecot-lmtp 
 +smtputf8_enable = no
 </code> </code>
  
 It's now time to instruct dovecot as to what authorization mechanisms to accept. Remember, plain login, contrary to popular belief, is not in any way insecure. Remeber that they both use Base64 encoding to begin with, which is then wrapped in TLS. Therefore, as long as you enforce TLS, there's very little security concern. Adding strong passwords with 14 or more characters and regularly rotating client credentials limits the surface vector even moreso. Okay, to setup the login mechanisms, open up ''/etc/dovecot/conf.d/10-auth.conf'' and configure the following parameters (updated for Trixie): It's now time to instruct dovecot as to what authorization mechanisms to accept. Remember, plain login, contrary to popular belief, is not in any way insecure. Remeber that they both use Base64 encoding to begin with, which is then wrapped in TLS. Therefore, as long as you enforce TLS, there's very little security concern. Adding strong passwords with 14 or more characters and regularly rotating client credentials limits the surface vector even moreso. Okay, to setup the login mechanisms, open up ''/etc/dovecot/conf.d/10-auth.conf'' and configure the following parameters (updated for Trixie):
  
-<code bash>+<code>
 auth_username_format = %{user|username|lower} auth_username_format = %{user|username|lower}
 auth_mechanisms = plain login auth_mechanisms = plain login
Line 255: Line 266:
 Now, let's enforce ssl so that the plain and login mechanisms above are wrapped - end to end - in TLS handshakes. It is also important to explicitly define the least secure and/or oldest TLS handshake your server will permit. Additionally, make sure that you comment out ssl_prefer_server_ciphers and/or anything like ''ssl_server_dh'' file, both of which are now deprecated. To do this, open up ''/etc/dovecot/conf.d/10-ssl.conf'' and edit the following parameters (updated for Trixie): Now, let's enforce ssl so that the plain and login mechanisms above are wrapped - end to end - in TLS handshakes. It is also important to explicitly define the least secure and/or oldest TLS handshake your server will permit. Additionally, make sure that you comment out ssl_prefer_server_ciphers and/or anything like ''ssl_server_dh'' file, both of which are now deprecated. To do this, open up ''/etc/dovecot/conf.d/10-ssl.conf'' and edit the following parameters (updated for Trixie):
  
-<code bash>+<code>
 ssl = required ssl = required
 ssl_server_cert_file = /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem ssl_server_cert_file = /etc/letsencrypt/live/mail.yourdomain.com/fullchain.pem
Line 264: Line 275:
 Add SASL listener in ''/etc/dovecot/conf.d/10-master.conf'' so that postfix can query dovecot and this socket for user credentials for incoming email. Combined with the configuration ''smtpd_sasl_path=private/auth'' in ''main.cf'', this also allows postfix to authenticate smtp requests. In all cases, dovecot leverages the UNIX users for credentials. Add SASL listener in ''/etc/dovecot/conf.d/10-master.conf'' so that postfix can query dovecot and this socket for user credentials for incoming email. Combined with the configuration ''smtpd_sasl_path=private/auth'' in ''main.cf'', this also allows postfix to authenticate smtp requests. In all cases, dovecot leverages the UNIX users for credentials.
  
-<code bash>+<code>
 service auth { service auth {
   unix_listener /var/spool/postfix/private/auth {   unix_listener /var/spool/postfix/private/auth {
Line 274: Line 285:
 </code> </code>
  
-Lastly, before testing, make sure that you only authorize your mynetworks and properly authenticated users. Failing to do this will mean your server could potentially be used for public relay. This block rejects any unauthenticated senders (besides localhost) and requires senders to be authenticated (or to be localhost) while only permitting incoming email directed to ''@haacksnetworking.org'' or ''@domain.com''Please note that if you continue with the optional configurations later in this tutorialyou will integrate these stanzas into other blocks.+If you have IMAP folders with lots of email, consider adjusting the ''imap'' block in ''/etc/dovecot/conf.d/10-master.conf''Make sure you are not editing ''imap-login''but rather ''service imap {''. This increases virtual memory and processes for dovecot.
  
-<code bash+<code
-smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, rejectsmtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination+service imap { 
 +    vsz_limit = 4096M 
 +    process_limit = 2048 
 +
 +</code> 
 + 
 +Similarly, for large IMAP folders, you also want to adjust IMAP's logic to provide 256k (vs. 64k) to each single IMAP command. Additionally, make sure that you raise simultaneous connections from a single IP to 25, 50, or more, so your client can handle more in parallel. Lastly, adjust the idle time notification as well for less latency and snappier results. The default is 2mins, but if this is your server only, you can safely go as low as 15s or 30s. This lowering not only helps Roundcube, but helps Delta Chat and other email clients as well. Open up ''/etc/dovecot/conf.d/20-imap.conf'' and add the following towards the bottom: 
 + 
 +<code> 
 +protocol imap { 
 +    imap_max_line_length = 256k 
 +    mail_max_userip_connections = 50 
 +    imap_idle_notify_interval = 15s 
 +
 +</code> 
 +  
 +Lastly, before testing, make sure that you only authorize your mynetworks and properly authenticated users. Failing to do this will mean your server could potentially be used for public relay. This block rejects any unauthenticated senders (besides localhost) and requires senders to be authenticated (or to be localhost) while only permitting incoming email directed to ''@haacksnetworking.org'' or ''@domain.com''. These blocks will be added later in the tutorial, but/and I bring them up now to emphasize that you should close off the public relay. 
 + 
 +<code
 +smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, permit 
 +smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination
 </code> </code>
  
 At this time, the bare minimum requirements are in place for sending and receiving email. You can either fire up Thunderbird and field test it all, or use swaks, ncat, telnet, or openssl to test the TLS handshakes. Here's the ones I use in addition to field testing with Thunderbird: At this time, the bare minimum requirements are in place for sending and receiving email. You can either fire up Thunderbird and field test it all, or use swaks, ncat, telnet, or openssl to test the TLS handshakes. Here's the ones I use in addition to field testing with Thunderbird:
  
-<code bash+<code> 
-openssl s_client -connect mail.yourdomain.com:465openssl s_client -connect mail.yourdomain.com:587 +openssl s_client -connect mail.yourdomain.com:465 
-openssl s_client -connect mail.yourdomain.com:993openssl s_client -starttls smtp -connect mail.yourdomain.com:25swaks --tls --server mail.yourdomain.com -p 587swaks --tls --server mail.yourdomain.com -p 465+openssl s_client -connect mail.yourdomain.com:587 
 +openssl s_client -connect mail.yourdomain.com:993 
 +openssl s_client -starttls smtp -connect mail.yourdomain.com:25 
 +swaks --tls --server mail.yourdomain.com -p 587 
 +swaks --tls --server mail.yourdomain.com -p 465
 </code> </code>
  
Line 300: Line 335:
 As with elsewhere, this tutorial does not use any of the policies for rejecting email - everything is accepted. As your server matures and you complete testing, you can optionally harden your server's policies, but bear in mind, this means you are explicitly configuring it to reject incoming email in some cases. So, if those rules aren't perfect, then you are guaranteeing the possibility of an invalid rejection, or a false negative. In lay terms, that means you are choosing to not get emails from people that send to you. So, proceed with caution if you choose to make server-wide reject rules, either in postfix generally and/or with your policies. Personally, I don't ever do that. I want to receive any and all incoming emails. Let's start this second part by installing our spf policy with ''sudo apt install postfix-policyd-spf-python''. After it's installed, you can configure the policy daemon and socket in ''/etc/postfix/master.cf'' by adding this at the bottom of the config: As with elsewhere, this tutorial does not use any of the policies for rejecting email - everything is accepted. As your server matures and you complete testing, you can optionally harden your server's policies, but bear in mind, this means you are explicitly configuring it to reject incoming email in some cases. So, if those rules aren't perfect, then you are guaranteeing the possibility of an invalid rejection, or a false negative. In lay terms, that means you are choosing to not get emails from people that send to you. So, proceed with caution if you choose to make server-wide reject rules, either in postfix generally and/or with your policies. Personally, I don't ever do that. I want to receive any and all incoming emails. Let's start this second part by installing our spf policy with ''sudo apt install postfix-policyd-spf-python''. After it's installed, you can configure the policy daemon and socket in ''/etc/postfix/master.cf'' by adding this at the bottom of the config:
  
-<code bash>+<code>
 #spf policy\ #spf policy\
-policyd-spf unix - n n - 0 spawn\ +policyd-spf  unix                                spawn 
-user=policyd-spf argv=/usr/bin/policyd-spf+    user=policyd-spf argv=/usr/bin/policyd-spf
 </code> </code>
  
 After that, open up ''/etc/postfix/main.cf'' and put the spf policy together with postfix's recipient restrictions. The reason I organize the block this way is because it makes logical sense to group together the criteria under which email is received, whether because it is postfix's general policy and/or your SPF policy. After that, open up ''/etc/postfix/main.cf'' and put the spf policy together with postfix's recipient restrictions. The reason I organize the block this way is because it makes logical sense to group together the criteria under which email is received, whether because it is postfix's general policy and/or your SPF policy.
  
-<code bash+<code> 
-#spf incoming policy and recipient restrictions\ +#spf incoming policy and recipient restrictions 
-policyd-spf_time_limit = 3600\ +policyd-spf_time_limit = 3600 
-smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policyd-spf+smtpd_recipient_restrictions =  
 +    permit_mynetworks,  
 +    permit_sasl_authenticated,  
 +    reject_unauth_destination, 
 +    reject_non_fqdn_recipient, 
 +    reject_unknown_recipient_domain, 
 +    check_policy_service unix:private/policyd-spf
 </code> </code>
  
Line 321: Line 362:
 Since I just took care of recipient restrictions, it makes sense to cover sender restrictions. Remember, in the base server, both recipient and sender configurations were covered in brief. If you do these recommended and optional steps, you should expand your sender restrictions block and include other forms of checking. Some of the those options are below. Please note that similar hardening can also be done via postfix on the recipient-side as well: Since I just took care of recipient restrictions, it makes sense to cover sender restrictions. Remember, in the base server, both recipient and sender configurations were covered in brief. If you do these recommended and optional steps, you should expand your sender restrictions block and include other forms of checking. Some of the those options are below. Please note that similar hardening can also be done via postfix on the recipient-side as well:
  
-<code bash>+<code>
 smtpd_sender_restrictions = smtpd_sender_restrictions =
-permit_mynetworks, +    permit_mynetworks,  
-permit_sasl_authenticated, +    permit_sasl_authenticated, 
-#reject_unknown_reverse_client_hostname, +    #reject_unknown_reverse_client_hostname, 
-#reject_unknown_client_hostname, +    #reject_unknown_client_hostname, 
-#reject_unknown_sender_domain, +    #reject_unknown_sender_domain, 
-reject_unauthenticated_sender_login_mismatch, +    reject_unauthenticated_sender_login_mismatch, 
-reject_sender_login_mismatch, +    reject_sender_login_mismatch, 
-permit+    check_sender_access hash:/etc/postfix/sender_access, 
 +    permit
 </code> </code>
 +
 +The sender_access file is super helpful for repeat offenders. Sure, you can also just blacklist those in sieve rules, but that means that they still arrive and fill up your Junk folder. In some cases, spammers will send 100s of the same message per day. In those cases, it is helpful to stop them at the gates with postfix's sender_access:
 +
 +  nano /etc/postfix/sender_access
 +  <spam@domain.com     REJECT> #block exact email
 +  <domain.com          REJECT> #block all users @domain.com
 +  <.domain.com         REJECT> #block all users @sub.domain.com
 +  sudo postmap /etc/postfix/sender_access
 +  sudo postfix reload
 +  sudo systemctl restart postfix
  
 Now that the SPF/recipient and PTRI am now going to set up the DKIM policy for incoming email but/and, I will also use the same package to cut a DKIM keypair for use with the server's outgoing email. First, I install the policy and add opendkim to the postfix group. Now that the SPF/recipient and PTRI am now going to set up the DKIM policy for incoming email but/and, I will also use the same package to cut a DKIM keypair for use with the server's outgoing email. First, I install the policy and add opendkim to the postfix group.
  
-<code bash>+<code>
 sudo apt install opendkim opendkim-tools sudo apt install opendkim opendkim-tools
 sudo adduser postfix opendkim sudo adduser postfix opendkim
Line 342: Line 394:
 Once that's done, it's time to open ''/etc/opendkim.conf'' and setup the configuration for the server's keypair: Once that's done, it's time to open ''/etc/opendkim.conf'' and setup the configuration for the server's keypair:
  
-<code bash>+<code>
 Canonicalization relaxed/simple Canonicalization relaxed/simple
 Mode sv Mode sv
Line 355: Line 407:
 It's now time to create the keys: It's now time to create the keys:
  
-<code bash>+<code>
 sudo mkdir -p /etc/opendkim/keys sudo mkdir -p /etc/opendkim/keys
 sudo chown -R opendkim:opendkim /etc/opendkim sudo chown -R opendkim:opendkim /etc/opendkim
 sudo chmod 711 /etc/opendkim/keys sudo chmod 711 /etc/opendkim/keys
-sudo mkdir /etc/opendkim/keys/yourdomain.com 
-sudo opendkim-genkey -b 2048 -d yourdomain.com -D /etc/opendkim/keys/yourdomain.com -s default -v 
-sudo chown opendkim:opendkim /etc/opendkim/keys/yourdomain.com/default.private 
-sudo chmod 600 /etc/opendkim/keys/yourdomain.com/default.private 
 </code> </code>
  
 After creating the keys, edit the signing table so that outgoing emails can verify against the keypair. Open up ''/etc/opendkim/signing.table'' and add: After creating the keys, edit the signing table so that outgoing emails can verify against the keypair. Open up ''/etc/opendkim/signing.table'' and add:
  
-<code bash+<code> 
-'*@yourdomain.com default._domainkey.yourdomain.com' +*@yourdomain.com default._domainkey.yourdomain.com 
-'*@*.yourdomain.com default._domainkey.yourdomain.com'+*@*.yourdomain.com default._domainkey.yourdomain.com
 </code> </code>
  
Line 376: Line 424:
   default._domainkey.yourdomain.com yourdomain.com:default:/etc/opendkim/keys/yourdomain.com/default.private   default._domainkey.yourdomain.com yourdomain.com:default:/etc/opendkim/keys/yourdomain.com/default.private
  
-Lastly, instruct you server to trust only localhost and your domain. Open up /etc/opendkim/trusted.hosts and enter:+Lastly, instruct you server to trust only localhost and your domain. Open up ''/etc/opendkim/trusted.hosts'' and enter:
  
-<code bash>+<code>
 127.0.0.1\ 127.0.0.1\
 localhost\ localhost\
Line 384: Line 432:
 </code> </code>
  
-Use the built-in tool to test your DKIM keypair:+Now, cut the keys:
  
-  sudo opendkim-testkey -d yourdomain.com -s default -vvv+  sudo mkdir /etc/opendkim/keys/example.com 
 +  sudo opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s default -
 +  sudo chown opendkim:opendkim /etc/opendkim/keys/example.com/default.private 
 +  sudo chmod 600 /etc/opendkim/keys/example.com/default.private
  
 It's now time to build your last DNS record. To do that, you need your public key you created above. To view that on the CLI, run the cat command below. You then take that output to your DNS host of choice, and create a TXT record containing this value. In the subdomain field, you enter your selector, i.e., default._domainkey and in the target, you enter the output of the command below. It's now time to build your last DNS record. To do that, you need your public key you created above. To view that on the CLI, run the cat command below. You then take that output to your DNS host of choice, and create a TXT record containing this value. In the subdomain field, you enter your selector, i.e., default._domainkey and in the target, you enter the output of the command below.
  
   sudo cat /etc/opendkim/keys/yourdomain.com/default.txt   sudo cat /etc/opendkim/keys/yourdomain.com/default.txt
 +
 +Once your DNS record for DKIM is created, use the built-in tool to test the record:
 +
 +  sudo opendkim-testkey -d yourdomain.com -s default -vvv
  
 Okay, now that your keypair for smtp has been created and DNS updated, it's a good time to create the server's DKIM policy. First, you need to give postfix access to opendkim's tooling: Okay, now that your keypair for smtp has been created and DNS updated, it's a good time to create the server's DKIM policy. First, you need to give postfix access to opendkim's tooling:
Line 407: Line 462:
 Now that the server's DKIM policy is configured, it's required to let postfix know of the policy, where the socket is, and how to leverage the policy (reject/accept). Please note that the other policies will add to and/or expand this block, notably the smtpd_milters stanza, which will expand with each additional policy and associated socket that gets added. Open up the main postfix configuration in ''/etc/postfix/main.cf'' and enter the following: Now that the server's DKIM policy is configured, it's required to let postfix know of the policy, where the socket is, and how to leverage the policy (reject/accept). Please note that the other policies will add to and/or expand this block, notably the smtpd_milters stanza, which will expand with each additional policy and associated socket that gets added. Open up the main postfix configuration in ''/etc/postfix/main.cf'' and enter the following:
  
-<code bash>+<code>
 milter_default_action = accept milter_default_action = accept
 milter_protocol = 6 milter_protocol = 6
Line 414: Line 469:
 </code> </code>
  
-The most important line is the uppermost line, which specifies that email should not be rejected as a result of leveraging the policy. Again, this ensures that spam assassin has what it needs to help users sort and organize email, without the possibility of phantom rejections, prohibiting email from having ever arrived in your inbox. After your DKIM keypair and DKIM policy are setup, you can setup a DMARC policy as well. Install the policy with sudo apt install opendmarc. After it installs, open up ''/etc/opendmarc.conf'' and enter the following:+The most important line is the uppermost line, which specifies that email should not be rejected as a result of leveraging the policy. Again, this ensures that spam assassin has what it needs to help users sort and organize email, without the possibility of phantom rejections, prohibiting email from having ever arrived in your inbox. After your DKIM keypair and DKIM policy are setup, you can setup a DMARC policy as well. Install the policy with ''sudo apt install opendmarc''. After it installs, open up ''/etc/opendmarc.conf'' and enter the following:
  
-<code bash>+<code>
 AuthservID OpenDMARC AuthservID OpenDMARC
 TrustedAuthservIDs mail.yourdomain.com TrustedAuthservIDs mail.yourdomain.com
Line 428: Line 483:
 Similarly to the DKIM policy, we need to give postfix permissions to the DMARC tooling and socket. It's also required to add opendmarc to the postfix group: Similarly to the DKIM policy, we need to give postfix permissions to the DMARC tooling and socket. It's also required to add opendmarc to the postfix group:
  
-<code bash>+<code>
 sudo mkdir -p /var/spool/postfix/opendmarc sudo mkdir -p /var/spool/postfix/opendmarc
 sudo chown opendmarc:opendmarc /var/spool/postfix/opendmarc -R sudo chown opendmarc:opendmarc /var/spool/postfix/opendmarc -R
Line 438: Line 493:
 Similarly to SPF and DKIM, it's essential to define the policy in the main postfix configuration file. Open up ''/etc/postfix/main.cf'' and add a block for dmarc. Expand the ''smtpd_milters'' section to include DMARC's socket. Note again how I specify accept so as to ensure proper logging for spam assassin without any possibility of phantom rejections, or email lost at the gates. Similarly to SPF and DKIM, it's essential to define the policy in the main postfix configuration file. Open up ''/etc/postfix/main.cf'' and add a block for dmarc. Expand the ''smtpd_milters'' section to include DMARC's socket. Note again how I specify accept so as to ensure proper logging for spam assassin without any possibility of phantom rejections, or email lost at the gates.
  
-<code bash>+<code>
 #dmarc policy #dmarc policy
 milter_default_action = accept milter_default_action = accept
Line 456: Line 511:
 First, let's add sieve to the local delivery agent over in /etc/dovecot/conf.d/15-lda.conf. This is essential for any time you are manually testing and/or processing emails on the shell, which will utilize the LDA instead of LMTP. This ensures that the local delivery agent is sieve-ready. Head over to ''/etc/dovecot/conf.d/15-lda.conf'' and edit the block: First, let's add sieve to the local delivery agent over in /etc/dovecot/conf.d/15-lda.conf. This is essential for any time you are manually testing and/or processing emails on the shell, which will utilize the LDA instead of LMTP. This ensures that the local delivery agent is sieve-ready. Head over to ''/etc/dovecot/conf.d/15-lda.conf'' and edit the block:
  
-<code bash>+<code>
 protocol lda { protocol lda {
   mail_plugins = $mail_plugins sieve   mail_plugins = $mail_plugins sieve
Line 464: Line 519:
 It is also imperative that LMTP be configured for using sieve as well. Head over to ''/etc/dovecot/conf.d/20-lmtp.conf'' and edit the block: It is also imperative that LMTP be configured for using sieve as well. Head over to ''/etc/dovecot/conf.d/20-lmtp.conf'' and edit the block:
  
-<code bash>+<code>
 protocol lmtp { protocol lmtp {
   mail_plugins = quota sieve   mail_plugins = quota sieve
Line 472: Line 527:
 As with any service, it's essential to configure postfix to use it. So, head to ''/etc/postfix/main.cf'' and add the spam assassin socket to the primary milter. Just as with DKIM and DMARC, you are expanding the existing smtpd_milters clause, not making redundant and/or duplicate stanzas. Head over to ''/etc/postfix/main.cf'' and edit the policy block we created above for DKIM, DMARC, and now spam assassin. As with any service, it's essential to configure postfix to use it. So, head to ''/etc/postfix/main.cf'' and add the spam assassin socket to the primary milter. Just as with DKIM and DMARC, you are expanding the existing smtpd_milters clause, not making redundant and/or duplicate stanzas. Head over to ''/etc/postfix/main.cf'' and edit the policy block we created above for DKIM, DMARC, and now spam assassin.
  
-<code bash>+<code>
 milter_default_action = accept milter_default_action = accept
 milter_protocol = 6 milter_protocol = 6
Line 492: Line 547:
 Sieve is already enabled in dovecot and postfix. That was done up above. Now, the tutorial is discussing how to leverage the sieve syntax or language to get desired user results for incoming email. The most basic way to do this is by setting up a global, or server-wide rule, that filters emails before dovecot, via lmtp, delivers the emails to their final destination. This is done by spam assassin adding custom fields and scoring to incoming email headers. The sieve plugin assesses these headers and then makes the correct determination for the final destination, which dovecot, via lmtp, carries out. In summary, spam assassin does the ranking and header-adding, sieve assesses the spam assassin scores and uses the global sieve rule to make determinations for all users, and finally dovecot+limtp handles the mail delivery. To create a global config for all users in this way, head over to ''/etc/dovecot/conf.d/90-sieve.conf'' and then add/uncomment this line: Sieve is already enabled in dovecot and postfix. That was done up above. Now, the tutorial is discussing how to leverage the sieve syntax or language to get desired user results for incoming email. The most basic way to do this is by setting up a global, or server-wide rule, that filters emails before dovecot, via lmtp, delivers the emails to their final destination. This is done by spam assassin adding custom fields and scoring to incoming email headers. The sieve plugin assesses these headers and then makes the correct determination for the final destination, which dovecot, via lmtp, carries out. In summary, spam assassin does the ranking and header-adding, sieve assesses the spam assassin scores and uses the global sieve rule to make determinations for all users, and finally dovecot+limtp handles the mail delivery. To create a global config for all users in this way, head over to ''/etc/dovecot/conf.d/90-sieve.conf'' and then add/uncomment this line:
  
-  sieve_before = /var/mail/SpamToJunk.sieve+<code> 
 +#sieve_before = /var/mail/SpamToJunk.sieve #old bookworm way and early Trixie
  
-Now, create the file that you just referenced above in ''/var/mail/SpamToJunk.sieve'' and enter the following:+#modern trixie way 
 +sieve_script SpamToJunk { 
 +  type = before 
 +  path = /var/mail/SpamToJunk.sieve 
 +
 +</code> 
 + 
 +Now, create the file that you just referenced above in ''/var/mail/SpamToJunk.sieve'' and enter the following and/or any sieve rules you would like.
  
-<code bash>+<code>
 require "fileinto"; require "fileinto";
 if header :contains "X-Spam-Flag" "YES" { if header :contains "X-Spam-Flag" "YES" {
Line 506: Line 569:
 Load the rule by issuing the ''sudo sievec /var/mail/SpamToJunk.sieve'' command. I would also recommend restarting postfix, dovecot, and spam assassin. Now, the header called X-Spam-Flag is populated with either a Yes or No response by the spam assassin service. Spam assassin determines the yes/no score based on the scoring rules specified in the configuration file. There are many other options and far more complex rules one can establish than this basic example. This is just to get folks started. Here's an example set of rules from the ''/etc/spamassassin/local.cf'' main configuration file: Load the rule by issuing the ''sudo sievec /var/mail/SpamToJunk.sieve'' command. I would also recommend restarting postfix, dovecot, and spam assassin. Now, the header called X-Spam-Flag is populated with either a Yes or No response by the spam assassin service. Spam assassin determines the yes/no score based on the scoring rules specified in the configuration file. There are many other options and far more complex rules one can establish than this basic example. This is just to get folks started. Here's an example set of rules from the ''/etc/spamassassin/local.cf'' main configuration file:
  
-<code bash>+<code>
 report_contact webmaster@yourdomain.com report_contact webmaster@yourdomain.com
 required_score 5.0 required_score 5.0
 report_safe 0 report_safe 0
-ifplugin Mail::SpamAssassin::Plugin::Shortcircuitendif # Mail::SpamAssassin::Plugin::Shortcircuit+ifplugin Mail::SpamAssassin::Plugin::Shortcircuit 
 +endif Mail::SpamAssassin::Plugin::Shortcircuit
 # uncomment the line below once unbound is working (optional) # uncomment the line below once unbound is working (optional)
 # dns_server 127.0.0.1score MISSING_FROM 5.0 # dns_server 127.0.0.1score MISSING_FROM 5.0
Line 536: Line 600:
 Download [[https://repo.haacksnetworking.org/haacknet/haackingclub/-/blob/master/configs/mailservers/spamassassin/local.cf?ref_type=heads|local.cf]]:  Download [[https://repo.haacksnetworking.org/haacknet/haackingclub/-/blob/master/configs/mailservers/spamassassin/local.cf?ref_type=heads|local.cf]]: 
  
-This basic configuration shows you where to place the rules/scoring, etc. The names above are referred to technically as symbolic headers and I found the examples above by searching documentation and/or forum hunting. I also spent time reviewing the rules for accuracy and testing with ''spamassassin --D < example.eml'' regularly until assassing scored emails properly. It takes time to perfec this, so keep it permissive for starters. Make sure to check logs regularly for errors/clues using ''journalctl -u spamass-milter -u postfix -u dovecot -u opendkim -u opendmarc''. This will help you track what is and is not working for spam assassin and for you, and to thereby adjust/alter/remove scores or change points to fit your use-case and preferences. The whitelist and blacklist options can be scaled as needed and are self-explanatory. This setup is very elegant and helpful for single user email servers and/or tight-knit and close groups of family/people. As more users are needed, the ability of a one-sizefits-all rule to meet everyone's individual needs becomes more and more difficult. For this reason, I chose to install Roundcube in order to leverage the filters feature in the webgui to more easily manage spam rules. Here's how to install Roundcube and use it to manage sieve.+This basic configuration shows you where to place the rules/scoring, etc. The names above are referred to technically as symbolic headers and I found the examples above by searching documentation and/or forum hunting. I also spent time reviewing the rules for accuracy and testing with ''sudo -u username sieve-test -C -D -t - /home/username/sieve/roundcube.sieve ./spam-message.eml'' regularly until assassing scored emails properly. It takes time to perfec this, so keep it permissive for starters. Make sure to check logs regularly for errors/clues using ''journalctl -u spamass-milter -u postfix -u dovecot -u opendkim -u opendmarc''. This will help you track what is and is not working for spam assassin and for you, and to thereby adjust/alter/remove scores or change points to fit your use-case and preferences. The whitelist and blacklist options can be scaled as needed and are self-explanatory. This setup is very elegant and helpful for single user email servers and/or tight-knit and close groups of family/people. As more users are needed, the ability of a one-sizefits-all rule to meet everyone's individual needs becomes more and more difficult. For this reason, I chose to install Roundcube in order to leverage the filters feature in the webgui to more easily manage spam rules. Here's how to install Roundcube and use it to manage sieve.
  
-<code bash>+<code>
 cd /var/www cd /var/www
 wget https://github.com/roundcube/roundcubemail/releases/download/1.6.1/roundcubemail-1.6.1-complete.tar.gz wget https://github.com/roundcube/roundcubemail/releases/download/1.6.1/roundcubemail-1.6.1-complete.tar.gz
Line 551: Line 615:
 Obviously, go check the git repo and make sure to download the latest stable version. Roundcube requires a database, so let's set that up: Obviously, go check the git repo and make sure to download the latest stable version. Roundcube requires a database, so let's set that up:
  
-<code bash>+<code>
 sudo mysql -u root sudo mysql -u root
 CREATE DATABASE roundcube DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; CREATE DATABASE roundcube DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
Line 566: Line 630:
 Earlier in the tutorial, you were instructed to setup a vhost for ''mail.haacksnetworking.org'' / ''mail.domain.com''. You already created vhost-mail.conf for the purposes of having an associated A record and website destination, which is helpful for complete validation of email's record health. Now, you are going leverage this certificate for the purpose of encrypting the apache2 front-end, or reverse proxy. Apache receives the public requests, and then the new vhost config, which enables the reverse proxy, forwards those requests upstream to the roundcube service. First, replace ''vhost-mail.conf'' with something like the following: Earlier in the tutorial, you were instructed to setup a vhost for ''mail.haacksnetworking.org'' / ''mail.domain.com''. You already created vhost-mail.conf for the purposes of having an associated A record and website destination, which is helpful for complete validation of email's record health. Now, you are going leverage this certificate for the purpose of encrypting the apache2 front-end, or reverse proxy. Apache receives the public requests, and then the new vhost config, which enables the reverse proxy, forwards those requests upstream to the roundcube service. First, replace ''vhost-mail.conf'' with something like the following:
  
-<code bash>+<code>
 <VirtualHost *:80> <VirtualHost *:80>
       ServerName mail.domain.com       ServerName mail.domain.com
       ServerAdmin email@email.com       ServerAdmin email@email.com
-      DocumentRoot /var/www/roundcube/+      DocumentRoot /var/www/roundcube/ #add public_html for latest RC
       ErrorLog ${APACHE_LOG_DIR}/roundcube_error.log       ErrorLog ${APACHE_LOG_DIR}/roundcube_error.log
       CustomLog ${APACHE_LOG_DIR}/roundcube_access.log combined       CustomLog ${APACHE_LOG_DIR}/roundcube_access.log combined
Line 594: Line 658:
 After that's done, edit the vhost-mail-le.conf file with something like the following: After that's done, edit the vhost-mail-le.conf file with something like the following:
  
-<code bash>+<code>
 <IfModule mod_ssl.c> <IfModule mod_ssl.c>
 <VirtualHost *:443> <VirtualHost *:443>
       ServerName mail.domain.com       ServerName mail.domain.com
       ServerAdmin email@email.com       ServerAdmin email@email.com
-      DocumentRoot /var/www/roundcube/+      DocumentRoot /var/www/roundcube/  #add public_html for latest RC
       ErrorLog ${APACHE_LOG_DIR}/roundcube_error.log       ErrorLog ${APACHE_LOG_DIR}/roundcube_error.log
       CustomLog ${APACHE_LOG_DIR}/roundcube_access.log combined       CustomLog ${APACHE_LOG_DIR}/roundcube_access.log combined
Line 623: Line 687:
 Since the certificate was already created, these configs can just be dropped in to the http vhost and https vhost, respectively. These two vhosts were created earlier by you (http) and subsequently by Let's Encrypt (https). It's now time to connect Roundcube to the database that was created earlier: Since the certificate was already created, these configs can just be dropped in to the http vhost and https vhost, respectively. These two vhosts were created earlier by you (http) and subsequently by Let's Encrypt (https). It's now time to connect Roundcube to the database that was created earlier:
  
-<code bash>+<code>
 cd /var/www/roundcube/config/ cd /var/www/roundcube/config/
 sudo cp config.inc.php.sample config.inc.php sudo cp config.inc.php.sample config.inc.php
Line 630: Line 694:
 <$config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcube';> <$config['db_dsnw'] = 'mysql://roundcube:pass@localhost/roundcube';>
 <$config['des_key'] = 'rcmail-!24ByteDESkey*Str';? <$config['des_key'] = 'rcmail-!24ByteDESkey*Str';?
-<$config['imap_host'] = 'startls://domain.com:143';>+<$config['imap_host'] = 'ssl://mail.domain.com:993';>
 <$config['smtp_host'] = 'tls://mail.domain.com:587';> <$config['smtp_host'] = 'tls://mail.domain.com:587';>
 <$config['enable_spellcheck'] = true;> <$config['enable_spellcheck'] = true;>
Line 637: Line 701:
 In addition to configuring the database and renaming the sample config, it's imperative to edit the plugins block at the end of the primary configuration as well. Add and/or replace the bottom plugin block as follows: In addition to configuring the database and renaming the sample config, it's imperative to edit the plugins block at the end of the primary configuration as well. Add and/or replace the bottom plugin block as follows:
  
-<code bash>+<code>
 $config['plugins'] = [ $config['plugins'] = [
   'archive',   'archive',
Line 647: Line 711:
   'debug_logger',   'debug_logger',
   'emoticons',   'emoticons',
-  'enigma',+  // 'enigma', //
   'filesystem_attachments',   'filesystem_attachments',
   'help',   'help',
Line 676: Line 740:
 At this point, you should be able to log in to mail.haacksnetworking.org / mail.domain.com using a common web browser and your credentials. Your user name is the UNIX user name, i.e., only the handle without the domain. So, my user name is jonathan, for example ... and without haacksnetworking.org at the end. Your password is whatever you adduser yourusername specified when creating your simple UNIX user. At this point, you want to open your web browser and login. Head to the Filters section under Settings. Click the cog at the top and select Edit Filter Set. In the block to the right, replace the contents with the following: At this point, you should be able to log in to mail.haacksnetworking.org / mail.domain.com using a common web browser and your credentials. Your user name is the UNIX user name, i.e., only the handle without the domain. So, my user name is jonathan, for example ... and without haacksnetworking.org at the end. Your password is whatever you adduser yourusername specified when creating your simple UNIX user. At this point, you want to open your web browser and login. Head to the Filters section under Settings. Click the cog at the top and select Edit Filter Set. In the block to the right, replace the contents with the following:
  
-<code bash>+<code>
 require ["fileinto"]; require ["fileinto"];
 # rule:[whitelist] # rule:[whitelist]
Line 691: Line 755:
 } }
 # rule:[spamcheck] # rule:[spamcheck]
-if anyof (header :contains "x-spam-status" "Yes", header :contains "x-spam-flag" "YES", header :contains "x-spam-level" "*****")+if anyof (header :matches "x-spam-status" "Yes", header :contains "x-spam-flag" "YES", header :contains "x-spam-level" "*****")
 { {
  fileinto "Junk";  fileinto "Junk";
Line 704: Line 768:
 As you can see in the asset above, you now have access to refined sieve rules for each user. Once I decided that I needed or wanted user-level spam controls, it no longer made sense for me to manage this exclusively on the CLI. For this reason, I added Roundcube and began managing spam for email accounts individually. When Trixie came out, Dovecot's sieve implementation needed a bit of fine-tuning. First, head to ''/etc/dovecot/conf.d/90-sieve.conf'' and change the block as follows: As you can see in the asset above, you now have access to refined sieve rules for each user. Once I decided that I needed or wanted user-level spam controls, it no longer made sense for me to manage this exclusively on the CLI. For this reason, I added Roundcube and began managing spam for email accounts individually. When Trixie came out, Dovecot's sieve implementation needed a bit of fine-tuning. First, head to ''/etc/dovecot/conf.d/90-sieve.conf'' and change the block as follows:
  
-<code bash>+<code>
 # Comment out the old block: # Comment out the old block:
 #plugin {  #plugin { 
Line 716: Line 780:
   active_path = ~/.dovecot.sieve   active_path = ~/.dovecot.sieve
 } }
 +</code>
 +
 +Lastly, Roundcube is a web gui. So, it is also important that your php handler is optimized and configured with more memory and extra fpm servers. I make the following php adjustments for snappier response time. In ''/etc/php/8.4/fpm/php.ini'', input:
 +
 +<code>
 +memory_limit = 1G
 +max_execution_time = 300
 +max_input_time = 300
 +</code>
 +
 +And, finally, in ''/etc/php/8.4/fpm/pool.d/www.conf'', I adjust the servers to help account for simultaneous connections:
 +
 +<code>
 +pm = dynamic
 +pm.max_children = 200
 +pm.start_servers = 20
 +pm.min_spare_servers = 10
 +pm.max_spare_servers = 20
 +pm.max_requests = 500
 +request_terminate_timeout = 0
 </code> </code>
  
 ====== Part V - Setting up pflogsumm ====== ====== Part V - Setting up pflogsumm ======
  
-It's important to be able to monitor how your setup is performing and what is or is not working correctly. No better way to do that than to get some analytics emailed to you each day. To do that, let's install pflogsumm and use rsyslog for logging. Install the packages sudo apt install pflogsumm rsyslog and then create the log rotation rule over in ''/etc/logrotate.d/postfix-log'' and enter the following:+It's important to be able to monitor how your setup is performing and what is or is not working correctly. No better way to do that than to get some analytics emailed to you each day. To do that, let's install pflogsumm and use rsyslog for logging. Install the packages ''sudo apt install pflogsumm'' rsyslog and then create the log rotation rule over in ''/etc/logrotate.d/postfix-log'' and enter the following:
  
-<code bash>+<code>
 /var/log/mail.log { /var/log/mail.log {
   missingok   missingok
Line 735: Line 819:
 You need to make sure to comment out or remove #mail.log from ''/etc/logrotate.d/rsyslog''. This is done so as to replace the default rotation. Now, I will create a script to run pflogsumm on the archives. Create ''/usr/local/bin/pflog-run.sh'' or something similar and enter the following: You need to make sure to comment out or remove #mail.log from ''/etc/logrotate.d/rsyslog''. This is done so as to replace the default rotation. Now, I will create a script to run pflogsumm on the archives. Create ''/usr/local/bin/pflog-run.sh'' or something similar and enter the following:
  
-<code bash>+<code>
 #!/bin/sh #!/bin/sh
 #/usr/sbin/logrotate -f /etc/logrotate.d/postfix-log #/usr/sbin/logrotate -f /etc/logrotate.d/postfix-log
Line 760: Line 844:
 Some of the spam assassin tooling that uses RBL will not work unless you use your own recursive DNS instead of, for example, ''8.8.8.8''. It should also be noted that using your own DNS is better from both a speed and privacy perspective, so setting this up kills two birds with one proverbial stone. I have a separate tutorial about this on my blog and it has an associated wiki entry as well. As you review these tutorials, please remember that unbound can be used for both LAN and WAN settings. For this tutorial, an email server, one would use the WAN configuration. Here's a concise recap of how to set that up, which you will do directly on the email server. First, let's install unbound sudo apt install unbound, by far the most hassle free way to do recursive DNS. Once it is up and running, it's time to put a WAN-based config in place in ''/etc/unbound/unbound.conf''. In that file, add the following: Some of the spam assassin tooling that uses RBL will not work unless you use your own recursive DNS instead of, for example, ''8.8.8.8''. It should also be noted that using your own DNS is better from both a speed and privacy perspective, so setting this up kills two birds with one proverbial stone. I have a separate tutorial about this on my blog and it has an associated wiki entry as well. As you review these tutorials, please remember that unbound can be used for both LAN and WAN settings. For this tutorial, an email server, one would use the WAN configuration. Here's a concise recap of how to set that up, which you will do directly on the email server. First, let's install unbound sudo apt install unbound, by far the most hassle free way to do recursive DNS. Once it is up and running, it's time to put a WAN-based config in place in ''/etc/unbound/unbound.conf''. In that file, add the following:
  
-<code bash>+<code>
 server: server:
   # Bind to localhost only   # Bind to localhost only
Line 839: Line 923:
 Next, open up ''/etc/logrotate.d/unbound'' and enter the rotation logic: Next, open up ''/etc/logrotate.d/unbound'' and enter the rotation logic:
  
-<code bash>+<code>
 /var/log/unbound/unbound.log { /var/log/unbound/unbound.log {
 daily daily
Line 853: Line 937:
 Next, disable systemd's resolver: Next, disable systemd's resolver:
  
-<code bash>+<code>
 systemctl disable --now unbound-resolvconf.service systemctl disable --now unbound-resolvconf.service
 sed -Ei 's/^unbound_conf=/#unbound_conf=/' /etc/resolvconf.conf sed -Ei 's/^unbound_conf=/#unbound_conf=/' /etc/resolvconf.conf
Line 903: Line 987:
 In the .xml file, put the following, adjusting for ''domain.com'': In the .xml file, put the following, adjusting for ''domain.com'':
  
-<code bash>+<code>
 <?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
 <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006"> <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
Line 958: Line 1042:
 If your client does not honor autodiscovery and/or you choose to enter manually, use the port recommendations and protocols above. Other options also exist. If your client does not honor autodiscovery and/or you choose to enter manually, use the port recommendations and protocols above. Other options also exist.
  
-====== Part VII - Additional Options ======+====== Part VIII - Additional Options ======
  
 The options below are results of small things that came up while using my own server over the last 5 years or so. First, I noticed that clients would not set up the standard directories and it turns out you need to tell dovevot to do that over in ''/etc/dovecot/conf.d/15-mailboxes.conf'' by enabling the ''auto = create'' in the folder blocks for which you desire auto-population. The options below are results of small things that came up while using my own server over the last 5 years or so. First, I noticed that clients would not set up the standard directories and it turns out you need to tell dovevot to do that over in ''/etc/dovecot/conf.d/15-mailboxes.conf'' by enabling the ''auto = create'' in the folder blocks for which you desire auto-population.
  
-<code bash>+<code>
 mailbox Drafts { mailbox Drafts {
   auto = create   auto = create
Line 979: Line 1063:
  
 Once you edited the file, load the changes with sudo postmap /etc/postfix/virtual_alias. Once you edited the file, load the changes with sudo postmap /etc/postfix/virtual_alias.
-Another thing I researched when reviewing Linux Babe's tutorial, but ultimately rejected doing was body and header inspection. To do that, install postfix's regular expression tooling with sudo apt install postfix-pcre and then edit the main configuration ''/etc/postfix/main.cf'' and enter the following stanzas:+Another thing I researched when reviewing Linux Babe's tutorial, but ultimately rejected doing was body and header inspection. To do that, install postfix's regular expression tooling with ''sudo apt install postfix-pcre'' and then edit the main configuration ''/etc/postfix/main.cf'' and enter the following stanzas:
  
   header_checks = pcre:/etc/postfix/header_checks   header_checks = pcre:/etc/postfix/header_checks
Line 995: Line 1079:
   sudo postmap /etc/postfix/body_checks   sudo postmap /etc/postfix/body_checks
  
-Before, during, and after the creation of this email server tutorial, I've had a need to use messaging/chat apps. I've used them all, whether Signal, Telegram, Nextcloud Talk, and loads of more boutique and experimental platforms. After years of debate with friends and colleagues, a friend suggested [[https://delta.chat/en/download|Delta Chat]], a chat app that - wait for it - uses email servers for chatting. Given my email server was already set up and purring, I gave it a try and I've used it since for family and business conversations, that is, small and trusted audiences. If you get through this tutorial, it'worth giving it a try! Just edit ''/etc/dovecot/conf.d/20-imap.conf'' and ensure the ''imap_idle_notify_interval = 1min'' idle notify interval is 1 or minsFor small use cases, increasing this frequency will harm nothing and improve the snappiness of the Delta Chat experienceEverything else is already perfectly compatible with Delta ChatJust export and save your keys!+Before, during, and after the creation of this email server tutorial, I've had a need to use messaging/chat apps. I've used them all, whether Signal, Telegram, Nextcloud Talk, and loads of more boutique and experimental platforms. After years of debate with friends and colleagues, a friend suggested [[https://delta.chat/en/download|Delta Chat]], a chat app that - wait for it - uses email servers for chatting. Given my email server was already set up and purring, I gave it a try and I've used it since for family and business conversations, that is, small and trusted audiences. All of the settings in this tutorial work perfectly for Delta Chat'classic email server option. The only setting you might want to tinker with if you use the server for Delta Chat is the ''imap_idle_notify_interval'' in ''/etc/dovecot/conf.d/20-imap.conf'' which I covered above. 
 + 
 +-- -- -- -- Miscellaneous Issues -- -- -- -- 
 + 
 +To test sieve's logic and/or to test spamassassin's scoring, download email (.eml) from Roundcube that did not behave as planned. Once you have the email, ''ssh'' into your mail server's simple unix user's mail directory and do the following: 
 + 
 +  su - username 
 +  cd ~/sieve 
 +  spamassassin -t -D < Amazon1.eml  
 +  # this ^^ gives you the headers / spamassassin scores in stdout 
 +  sieve-test -D -e roundcube.sieve Amazon3.eml  
 +  # this ^^ tests your sieve logic to see if the email goes in ham or spam 
 + 
 +In the output that follows, look for something like the below. I put example output for each command above, respectively: 
 + 
 +<code> 
 +Content analysis details:   (16.9 points, 5.0 required) 
 + 
 + pts rule name              description 
 +---- ---------------------- -------------------------------------------------- 
 + 0.8 DKIM_ADSP_ALL          No valid author signature, domain signs all mail 
 + 0.0 SPF_HELO_NONE          SPF: HELO does not publish an SPF Record 
 +-0.0 SPF_PASS               SPF: sender matches SPF record 
 + 0.2 BAYES_999              BODY: Bayes spam probability is 99.9 to 100% 
 +                            [score: 1.0000] 
 + 3.5 BAYES_99               BODY: Bayes spam probability is 99 to 100% 
 +                            [score: 1.0000] 
 + 2.0 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail 
 +                            domains are different 
 + 2.1 HTML_IMAGE_ONLY_12     BODY: HTML: images with 800-1200 bytes of words 
 + 0.0 HTML_EXTRA_CLOSE       BODY: HTML contains far too many close tags 
 + 0.1 MIME_HTML_ONLY         BODY: Message only has text/html MIME parts 
 + 0.0 HTML_MESSAGE           BODY: HTML included in message 
 + 2.0 RDNS_DYNAMIC           Delivered to internal network by host with 
 +                            dynamic-looking rDNS 
 + 0.0 HTML_SHORT_LINK_IMG_2  HTML is very short with a linked image 
 + 3.6 HTML_TAG_BALANCE_CENTER Malformatted HTML 
 + 0.4 HTML_MIME_NO_HTML_TAG  HTML-only message, but there is no HTML tag 
 + 1.0 GOOG_STO_IMG_HTML      Apparently using google content hosting to avoid 
 +                            URIBL 
 + 1.2 DMARC_QUAR             DMARC quarantine policy 
 + 0.0 T_REMOTE_IMAGE         Message contains an external image 
 +</code>
  
-If other quirky issues come up, I'll besure to add them right here!+<code> 
 +sieve-test: Debug: sieve: Commit fileinto action 
 +sieve-test: Debug: sieve: fileinto action: Commit storing into mailbox 'Junk' 
 +sieve-test: Info: sieve: msgid=<20250@psyb.info>: fileinto action: stored mail into mailbox 'Junk' 
 +info: msgid=<20250@psyb.info>: fileinto action: stored mail into mailbox 'Junk'
 +sieve-test: Debug: sieve: Finished finalizing actions (status=ok, keep=none, committed=yes) 
 +</code>   
  
-====== Part VIII - What's next? ======+====== Part IX - What's next? ======
  
 Next Steps Next Steps
Line 1011: Line 1143:
 I rewrote the mail server tutorial for the presentation [[https://tech.haacksnetworking.org/2025/06/10/your-email-your-rules-self-hosting-simplified/|Your Email, Your Rules: Self-Hosting Simplified]]. The SeaGL presentation can be found [[https://pretalx.seagl.org/2025/talk/VLM7AS/|on their calendar]]. I rewrote the mail server tutorial for the presentation [[https://tech.haacksnetworking.org/2025/06/10/your-email-your-rules-self-hosting-simplified/|Your Email, Your Rules: Self-Hosting Simplified]]. The SeaGL presentation can be found [[https://pretalx.seagl.org/2025/talk/VLM7AS/|on their calendar]].
  
- --- //[[alerts@haacksnetworking.org|oemb1905]] 2025/11/05 03:41//+ --- //[[alerts@haacksnetworking.org|oemb1905]] 2026/08/31 00:07//
computing/mailserver-trixie.1762316339.txt.gz · Last modified: by oemb1905