Translation

This post is also available in Simplified Chinese.

Another year somehow passed while I was idling away, and this novice has made no progress—if anything, I have become worse. My GPA hit a new low this semester, and I could already picture myself becoming a Yuquan-campus shut-in next term. Fortunately, today this shut-in finally finished configuring a personal mail server. My new address is wu@x-nagi.com; feel free to send me mail.

Mail Server

Basic Configuration

The most reliable guide I found was this DigitalOcean tutorial. Account information for every virtual user is stored in the virtual_users database. All mail is therefore sent and received by a single vmail user, independently of actual Linux login names. Aliases can be configured in virtual_aliases: mail addressed to the source column is delivered directly to the mailbox in destination.

To add a user, insert a row into the database (set the ID to NULL for automatic incrementing):

1
2
INSERT INTO `servermail`.`virtual_users` (`id`, `domain_id`, `password`, `email`) VALUES
(NULL, '1', ENCRYPT('firstpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'email1@example.com');

Following the tutorial should work without much trouble, although I did not try SpamAssassin in step 5. Note that some hosting providers block port 25 by default, causing mail delivery to fail mysteriously. In that case, switch providers or submit a support ticket asking for the port to be unblocked.

Once everything is configured, try connecting with a mail client such as Thunderbird. If it cannot connect, first run netstat -tulpn to check whether the required ports are open:

Thunderbird mail-server configuration

After logging in successfully, send a few messages to other mailboxes. They should generally arrive.

Advanced Configuration

Most messages sent with the basic setup were classified as spam by other mail services. The domain was still missing records that establish the trustworthiness of its mail, so mechanisms such as SPF were needed to improve its reputation.

PTR Record

Set a PTR record (reverse DNS) for the mail server’s IP address pointing to the mail domain. This demonstrates that the sending server actually belongs to that domain. Since a PTR record belongs to an IP address, it must be configured through the hosting provider. SPF and the records below are instead configured through the domain provider.

SPF and DMARC

Detailed explanations of SPF syntax are readily available. All that is required is a TXT record on the domain. For example, I added "v=spf1 a mx -all", which permits mail only when the sender’s IP appears in the domain’s A or MX records.

DMARC is another TXT record, placed under the _dmarc subdomain. Running dig -t TXT _dmarc.x-nagi.com shows "v=DMARC1; p=quarantine; rua=mailto:admin@x-nagi.com". The rua field specifies the administrator to notify when someone detects spoofed mail. Surprisingly, I received a periodic report only one day after configuring it.

DKIM

DKIM is slightly more involved. It signs each outgoing message, and the recipient verifies that signature with a public key stored in a DNS TXT record. Postfix can add signatures through OpenDKIM; see another DigitalOcean tutorial.

After all of these are configured, messages should generally stop being classified as spam. The setup can be tested with www.mail-tester.com, though DNS records take time to propagate.

Encryption

Thunderbird used encrypted ports and protocols such as 993 and 587, so communication between my local machine and server was encrypted. Yet when I sent test messages to Gmail, their details showed a red lock: the messages were not encrypted in transit. Postfix therefore needed STARTTLS. Postfix runs SMTP and communicates with other MTAs to send and receive mail; Dovecot runs IMAP and retrieves mail from the server to a local client.

My mail domain is the root domain, for which I had already obtained an SSL certificate while setting up HTTPS, so I could reuse it. Oddly, even though the certificate and key had been specified earlier, transport was still unencrypted. It turned out encryption had not actually been enabled. Add these four lines to Postfix’s main.cf:

1
2
3
4
smtpd_tls_security_level = may
smtp_tls_security_level = may
smtp_tls_loglevel = 1
smtpd_tls_loglevel = 1

After bothering Gmail with another test, the message details displayed a gray lock, indicating that TLS had been used in transit.

GPG

Update (2026-09-29): My current public key is ed25519/0xA298A079BA8CA493, with fingerprint AA6C 1957 5529 9CBD D1D4 E2AD A298 A079 BA8C A493. See the GPG Key page for the complete public key. Here is a message signed with the new key using --clearsign:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

TOO YOUNG TOO SIMPLE, SOMETIMES NAIVE!
-----BEGIN PGP SIGNATURE-----

iHUEARYKAB0WIQSqbBlXVSmcvdHU4q2imKB5uoykkwUCarsTtQAKCRCimKB5uoyk
k6OsAP0ddqnjhwoq4HJWwydK83m9Z9Mt5pzKFYWq2w7/M5aT+QD/cHSJsh0oQetf
yq5JInYksijycYUbCxs1jZpjsQmNsAk=
=fftP
-----END PGP SIGNATURE-----

Proton Mail turned out to be a good mail service. With my own GPG key, I could exchange encrypted and signed messages with Proton Mail users—and certainly not because this shut-in could not afford an S/MIME certificate.