Running a Mail Server and Publishing a GPG Key
Contents
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):
| |
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:

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:
| |
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:
| |
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.