My Little Corner of the Net

Setting Up a Mail Server with Chasquid and Dovecot – Part 3: Final Loose Ends

If you’ve been following along with the first two parts of my email server journey (Part 1, Part 2), you’ll now have a mail server set up and serving mail for your domains. In this third and final installment, I’m going to look at aliases, certificate renewal, “send-only” email accounts, and the “sendmail” program. I’ll also touch on setting up auxiliary services like SpamAssassin and ClamAV, even though I’m not currently using them in my setup.

Aliases

Aliases are an important aspect of any email system. They’re what allow for the maintainer of a website to receive mail sent to webmaster in her primary mailbox and for email groups, where multiple recipients get messages sent to a single address. Chasquid has rather advanced support for alias handling and supports these, and several other use cases.

The aliases File

You may recall, in Part 2, that we created a file named aliases for each domain we added. This is where our aliases will be stored. The format of this file is similar to aliases files for most other SMTP servers. The biggest exception to Chasquid’s setup, compared with other systems I’ve used, is that Chasquid has a file-per-domain setup by default, so it isn’t necessary to specify the domain part of the address for every alias.

The aliases file is basically a flat-file database (one record per line) with the following format:

alias: recipient

So, if Molly is managing my website and I want her to receive messages sent to webmaster, I’d add:

webmaster: molly

If Molly prefers to get her mail at her own domain, we can do that as well:

webmaster: me@molysmail.com

If I want everyone on sales team to get all messages sent to ‘sales@mydomain.com,’ I just separate the recipients with commas:

sales: steve, joe, sally

Aliases can resolve to other aliases. Chasquid restarts the resolution process for each recipient address it finds on an alias match and continues this process until it either delivers the message to a mailbox or forwards the message to a remote server. However, in order to prevent endless loops, Chasquid will fail after ten hops.

sales: steve, joe, sally

# When a message is sent to 'superdupersale', it will get forwarded to 'sales'
# Chasquid will see that 'sales' is also an alias and then send the message
# to each of the 'sales' recipients: Steve, Joe, and Sally
superdupersale: sales

Aliases files are reloaded by Chasquid automatically, so changes are picked up without needing to restart the service.

Chasquid processes the entire aliases file from top to bottom each time a message is received. If the local part (i.e. username) of the address is found more than once, only the last one will be used.

# this line will be ignored because another 'webmaster' entry appears after it
webmaster: me@mollysmail.com

# all mail for webmaster will go to molly (at the current domain) because the
# last entry is the one that is used
webmaster: molly

Chasquid will also stop processing the address after an alias is fully resolved, so if both an alias and a mailbox exist with the same local part, the message will not get delivered to the mailbox.

Drop Characters and Suffix Separators

Chasquid has two handy features in how it processes aliases: drop characters and suffix separators. Both can be specified in the chasquid.conf file in the servers configuration directory (usually /etc/chasquid):

suffix_separators: "+"
drop_characters: "."

Drop characters are simply dropped from the local part of the address before the alias resolution begins. By default, the only drop character is the period (“.”), so periods are ignored when attempting to match aliases.

If a message comes in addressed to john.smith@mydomain.com (or even j.o.h.n.s.m.i.t.h@mydomain.com), the periods will be ignored and the address will match to an alias for johnsmith. However, if an explicit alias for john.smith exists, that one will be matched before the period is dropped.

Suffix separators remove the first instance of the separator character and everything following it from the address for the purpose of address resolution. The default suffix separator is the plus sign (“+”).

If a message comes in for john+smith@mydomain.com, the +smith will be removed, resulting in a match on just john.

Suffix separators were popularized by email services like GMail and are often used as a way to track spam. For example, when signing up for an account on scummysite.com, you might use me+scuummysite@mydomain.com as your email address. When scummysite.com sends you a message, it still goes to your regular inbox, but you can then use filtering tools to move the message to a different folder or to delete it outright, based on the recipient address, which doesn’t get overwritten. And you’ll know that it was scummysite.com that sold your email address when you start seeing emails from scummiersite.com coming to the me+scummysite address. Suffix separators are also sometimes used by applications, such as mailing list managers, to indicate list management commands.

As I mentioned, Chasquid defaults to using periods for drop characters and plus signs for suffix separators by default. If you’d like to change the defaults and/or add additional characters, you’ll need to uncomment the two lines from the chasquid.conf file referenced above. Each character in the string will be considered it’s own drop character (or suffix separator), so this example would drop any periods or underscores:

drop_characters: "._"

Pipe Aliases

Sometimes you don’t want messages to be delivered to a mailbox, but instead to be processed by a program. Examples of this include ticketing systems, where messages to an address like support@mybusiness.com get logged as tickets, or maiing lists, where messages get forwarded to larger, more regulated lists of recipients than is practical with simple group aliases.

Chasquid supports this with “pipe” aliases. When an alias recipient starts with the pipe (“|”) character, Chasquid will use what follows it as a path to a program that it will try to run, and then pass the contents of the message to the program through standard input.

support: | /usr/local/bin/process-ticket

The message is passed in exactly as Chasquid receives it, including all headers. It is up to the receiving program to parse the message and interpret it however it needs.

While it’s not explicitly documented, I discovered that Chasquid will bounce the message (send an error back to the sender) if the piped program returns a non-zero exit status.

Catch-alls

Like many SMTP servers, Chasquid uses the asterisk (“*”) as a catch-all address. When an alias is set using that as the local part, all mail that does not get directed somewhere else will be forwarded to whatever recipient is specified.

*: badmail

Catch-alls should generally be avoided, since they don’t signal back to the sender that an address is not valid and thus catch-all mailboxes tend to collect a lot of spam. They can, however, be helpful for short-term use in testing and debugging, and they can be useful in certain applications, when connected to a pipe.

Chasquid also has an experimental feature (and I haven’t tested it, so I have no idea if it is currently supported by the version of Chasquid that’s currently available in the Debian distros) where an asterisk on the destination side of the alias will be replaced with the local part of the original address. This can be useful when redirecting all mail from one domain to another, such as when a business changes its name or when you have alternate domain names and you want all mail to end up at the primary.

# consider this definition in the mydomain.net aliases file
# any mail sent to any address on mydomain.net will be automatically forwarded
# to the same recipient on mydomain.com
*: *@mydomain.com

The alias-resolve Hook

So far, everything we’ve looked at has been configured through the domain’s aliases file, but chasquid has one more trick up it’s sleeve: the alias-resolve hook. Chasquid will look for a program named alias-resolve inside the hooks directory in the Chasquid configuration directory (so /etc/chasquid/hooks/alias-resolve on most systems). If the file is there and is executable, Chasquid will pass the recipient address as a command line argument. The program can then return a string that follows the format of the right-hand side of of the aliases file and Chasquid will use that to further resolve the alias. If no match is found, the script should exit without any output. This can be a handy way to look up aliases from other sources, such as searching a database or checking another program’s configuration files

There is only one alias-resolve program per server (though you could use it to call a domain-specific subprocess if needed).

There’s a few other less common things you can do with aliases that I haven’t discussed, such a “via” aliases, that let you direct messages on to a recipient on a specific remote server, so be sure to check the documentation. I’m only covering the more common uses.

Certificate Renewal

You’ll recall, from part two, that I opted to use Lego to handle obtaining TLS certificates for my email domains. Although Chasquid is designed to work with Certbot, my use of the Caddy web server caused conflicts. Lego can interface easily with my PowerDNS-based DNS servers to handle ACME challenges, avoiding these conflicts.

To renew a domain’s certificate, you call Lego with the a command like this:

lego --dns pdns --accept-tos --email $TLS_EMAIL --path /etc/chasquid/.lego -d mail.mydomain.com renew

Lego will then check the certificate and, if it is within the threshold for renewal (by default, that’s within 30 days of expiration), it will request a renewal from Let’s Encrypt. If not it will return an error message explaining why it was not renewed.

Unlike Certbot, which has a handy option that will check all of a system’s certificates and renew any of them that are within 30 days of expiration automatically, Lego has no such option. Instead, we must manually check each certificate. To automate this, I’ve written a script, which you can find in my shell scripts GitHub repo that I introduced in part two. The script, which I run once a week, will renew any certificate that’s due to expire in less than 30 days and then only restart the mail servers if any renewals happen.

Download the script and copy it to /root/bin or any other suitable location and give it executable permissions.

sudo wget -O /root/bin/renew-email-certs https://raw.githubusercontent.com/jpitoniak/shell-scripts/refs/heads/main/email-server/renew-email-certs
sudo chmod 700 /root/bin/renew-email-certs

You’ll also need to create a /root/.emailconfig file to store the PowerDNS API credentials, but if you’re already using my scripts from part two, you’ll likely already have this.

cat << EOF | sudo tee /root/.emailconfig
export PDNS_API_URL=https://dnsapi.yourdoamin.com
export PDNS_API_KEY=YOUR_API_KEY
TLS_EMAIL=you@yorudomain.com
EOF

Remember to add your actual API URL and key, of course. Then set a cron job to run the script at a regular interval.

sudo crontab -e

I run mine every Thursday at about 4:15 am, but chose a random time (not midnight) so as not to overwhelm the Let’s Encrypt systems.

15 4 * * 4 /root/renew-email-certs

If you have a MAILTO line at the top of your crontab (and you’ve already set up the local mail handling that’s explained in the “Sendmail” section below), you’ll get an email with the status of all of your certificates every time the script runs. Once it’s running, there should be no certificate maintenance needed, as the script will ensure that all of your email certificates stay up to date.

“Send-only” accounts

We addressed creating email accounts in part two, but sometimes you don’t need a full email account with a mailbox. Sometimes you just want to be able to send messages and don’t need to receive them.

Remember that Chasquid doesn’t allow for any kind of open relays, so in order to send messages through Chasquid servers, you must have an account. This includes applications that need to send mail of behalf of users.

The full-scale mailboxes we created in part two were actually Dovecot accounts and Chasquid was configured to use Dovecot to authenticate them, but Chasquid has it’s own authentication system that can be handy for this use case. To create a Chasquid only account, which will be able to send, but not receive mail, run the following command:

sudo chasquid-util user-add user@domain.com

You’ll be prompted for your desired password and then the account will be created. You can use it to authenticate with any SMTP client or library in order to send mail. The email address will only be used in the SMTP envelope when Chasquid interacts with other mail servers. Chasquid will not overwrite the From: header in the email message, so recipients will see their mail as coming from the senders they expect and will be able to reply without any issues.

I typically set up these accounts using addresses like appname@myprimarydomain.com. It’s unlikely that anyone will ever see this address, let alone have a reason to send a message to it, but if you do want to receive mail sent it, you can, you’ll just need to set an alias to forward it to a different account.

Sendmail

One of the first SMTP servers for Unix was called Sendmail. Although it still exists, most Linux systems have switched to other MTA (mail transfer agent) tools such as Exim or Postfix (or in our case, Chasquid). Sendmail’s roots run deep, however, as the sendmail program it included, which could be used for sending messages from the command line, is still used by lots of programs that need to send messages. These include cron and even the PHP mail() function, among others. Most MTAs include a symbolic link from Sendmail’s standard path, /usr/sbin/sendmail on most Linux distros, so as not to break this functionality.

Chasquid, likely as part of it’s no open relays policy, does not include a Sendmail-compatible interface, but another program, msmtp, can be used to implement it. msmtp is a Sendmail-compatible command-line SMTP client. All it does is accept messages from the command line and then relays them to another SMTP server, local or remote.

To install msmtp, run the following:

sudo apt update
sudo apt install msmtp-mta -y

This will also install the base msmtp package, extending that package with the symlink for sendmal.

msmtp looks for a configuration file at /etc/msmtp in which the receiving SMTP server credentials are stored. Prior to Debian Bookworm, msmtp had it’s SetGID bit set, meaning that it always ran under the msmtp user group. This let the permissions on the config file be set such that the msmtp program could read it but the user calling it could not, ensuring that users couldn’t find the SMTP server password. This changed in starting in Bookworm, so now we need to jump through a few more hoops. (It’s also possible to set user-specific configurations instead of a system-wide one, avoiding all of this, but this will require setting up an account for each user. Six to one; half-dozen to the other.)

msmtp-mta now includes a small server program msmtpd that is supposed to function as a local proxy server to get around the issues that prevent the SetGID approach from working now. Unfortunately, its use is not documented anywhere, and I was unable to get it to work. Instead, I found a similar Python script, mailproxy, and I’m using that to create a localhost-only mail server that passes mail to Chasquid.

To install mailproxy, run the following:

sudo wget -O /usr/local/bin/mailproxy https://raw.githubusercontent.com/kz26/mailproxy/refs/heads/master/mailproxy.py
chmod 755 /usr/local/bin/mailproxy

Mailproxy will run from it’s own user account:

adduser --system --no-create-home --group mailproxy

You will also need to create a SystemD unit file to manage running mailproxy as a service:

echo << EOF | sudo tee /etc/systemd/system/mailproxy.service
[Unit]
Description=MailProxy SMTP Proxy Service

[Service]
ExecStart=/usr/local/bin/mailproxy /etc/mailproxy.ini

# Disable Python's buffering of STDOUT and STDERR, so that output from the
# service shows up immediately in systemd's logs
Environment=PYTHONUNBUFFERED=1

Restart=on-failure
User=mailproxy

[Install]
WantedBy=multi-user.target
EOF

And a mailproxy.ini file with your Chasquid credentials. This is a good use for a “send-only” account:

echo << EOF | sudo tee /etc/mailproxy.ini
[local]
host = 127.0.0.1
port = 2525

[remote]
host = 127.0.0.1
port = 465
use_ssl = yes
starttls = no
smtp_auth = yes
smtp_auth_user = user@yourdomain.com
smtp_auth_password = YOUR_EMAIL_PASSWORD
EOF

sudo chown mailproxy:mailproxy /etc/mailproxy.ini
sudo chmod 640 /etc/mailproxy.ini

This sets up a tiny mail “server” listening on port 2525 of localhost. (The [local] port is arbitrary. You could use port 25, but depending on your Chasquid configuration, port 25 could be in use or otherwise blocked, so it’s probably best to use something else.) This server will pass mail it receives on to the Chasquid server for delivery using the credentials set in the [remote]. Be sure to set the correct user and password, and then start up the service by running:

sudo systemctl enable mailproxy.service
sudo systemctl start mailproxy.service

Now configure msmtp to send the mail it receives through the mailproxy service:

echo << EOF | sudo tee /etc/msmtprc
account default
host 127.0.0.1
port 2525
EOF

Now you should be able to send a message using something like this:

/usr/sbin/sendmail --read-envelope-from -i -t << EOF
From: me@myserver.com
To: me@someplaceelse.com
Subject: Hello?

If all goes well, this should be delivered.
EOF

SpamAssassin and ClamAV

As I mentioned in Part 1, I’m using a service called MXGuardDog to prefilter my incoming mail for spam, so I have not installed any spam or virus handling tools on the server as nothing bad should be getting through. Since not everyone will be doing this, though, I wanted to touch on both of them briefly.

SpamAssassin is an open source spam filtering tool. It uses text analysis, blacklists, Bayesian filtering, and other tools to analyze a message and assign it a spam score. if the score exceeds a certain threshold, the message is considered spam and, in most cases, it will be delivered to a spam folder instead of the inbox.

Similarly, ClamAV is a virus scanner. While it can be used similar to a desktop virus scanner like McAfee to scan an entire Linux machine, here we’d be using it to scan incoming messages for problematic attachments.

To install SpamAssassin and ClamAV, run the following:

sudo apt update
sudo apt install --install-recommends install spamassassin -y
sudo apt install clamav clamav-daemon -y

If you’re using the post-data script that was distributed with the chasquid package, there’s nothing more to do. If it finds either or both of them installed, Chasquid will use them when it runs it’s post-data hook on a message. You may, however, want to check both packages’ documentation and adjust their configurations to best meet your needs.

Wrapping Up

So there we have it. If you followed all three parts of this series, you now have a working SMTP server receiving mail for all of your domains and a working POP3/IMAP4 server for delivering that mail to your users. Both are securely locked down and use encryption for all client-to-server connections. You’ve set up aliases where you need them and you’ve set up your web and command line apps so that they can send mail, too, and you’ve explored the options for spam handling.

I’ll likely write a few more related posts as I continue to work through my email setup. These will likely include a deep-dive into the spam-prevention techniques provided by DMARK, DKIM, and SPF, setting up a mailing list manager that works with Chasquid, and setting up related services, like webmail, so be sure to keep watching for new posts.

Note: I may receive service credits from MXGuarddog for mentioning them here. Regardless, they’ve been a great provider who I’ve been paying a modest sum to manage my spam for years, and my recommendation is genuine.

Setting Up a Mail Server with Chasquid and Dovecot – Part 2: Domains and Accounts

In my last post, I explained how I went about setting up a new email server using Chasquid and Dovecot.  This post will build on that, so be sure to read that one first, if you haven’t already.
Today we’ll look at what’s required to actually get mail delivered to the new server.  We’ll first set up a new domain, and then add email accounts to it.

TLDR: This post outlines the process for setting things up.  If you don’t care about the details, I have a set of shell scripts that I’ve written to automate most of this.

Adding an Email Domain

To add an email domain to the server, we need to set up a bunch of files and directories.  Replace example.com with the actual email domain, obviously.

sudo mkdir /etc/chasquid/{domains,certs}/example.com
sudo touch /etc/chasquid/domains/example.com/{aliases,passwd,users}
sudo chown -R chasquid:chasquid /etc/chasquid/domains
sudo chown -R dovecot.dovecot
sudo chown dovecot:dovecot /etc/chasquid/domains.example.com/passwd
sudo chmod 0660 /etc/chasquid/domains/example.com/*

This creates directories under /etc/chasquid for the configuration (domains) and certificates (certs) for the example.com domain.

We then add three files: aliases, which will hold any email aliases that are set up on the domain; passwd, which will contain the usernames and passwords for email accounts added to the domain; and users, which will contain the account information for any Chasquid-only user accounts.  Chasquid accounts are handy when you want to be able to send mail, such as from an application, but don’t need an accompanying mailbox to go with it.  We’ll cover these in the next post.

Once everything is created, we set the ownership of the files to the user running chasquid (or, in the case of the passwd file, the user running Dovecot) to ensure that those users can write to the files.

DKIM Signing

DKIM (DomainKeys Identified Mail) is an anti-spam measure where the sending server signs outgoing messages using a private key.  The associated public key is published to the domain’s DNS records, where a receiving server can access it and verify that the message came from an authorized sender.  Many large email providers, including GMail and Microsoft, are now requiring domains that send large volumes of mail to use DKIM or risk having their mail treated as spam.  Even if your domain doesn’t reach the threshold to require it, it’s still a smart idea to use DKIM.

The latest versions of Chasquid (>=1.14) have built-in DKIM signing, but unfortunately the version currently installed by Debian Bookworm is a bit older (1.12 as of this writing), so we’ll need to install a third-party tool to do the work.

I’m currently using the driusan/dkim DKIM utilities.  These are written in Go and require compiling, so you’ll need to install the Go compiler (note: Go 1.25.1 was current at the time of writing, be sure to check the Go downloads page for the most up-to-date version).

wget https://go.dev/dl/go1.25.1.linux-amd64.tar.gz
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.25.1.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin

Then run the following commands to build and install the dkimsign, dkimverify, and dkimkeygen utilities:

go install github.com/driusan/dkim/cmd/dkimsign@latest
go install github.com/driusan/dkim/cmd/dkimverify@latest
go install github.com/driusan/dkim/cmd/dkimkeygen@latest
sudo cp ~/go/bin/{dkimsign,dkimverify,dkimkeygen} /usr/local/bin

Next, we need to create a DKIM signing key.  This needs to be done for each domain.
First, switch to the domain’s directory under /etc/chasquid/certs:

cd /etc/chasquid/certs/example.com

Then run the dkimkeygen command:

sudo dkimkeygen

This will create two files in the directory, private.pem which contains the private key, and dns.txt which contains the public key in the format needed for the DNS record.  Chasquid needs the key file to be named dkim_privkey.pem.

sudo mv private.pem dkim_privkey.pem

Next, you’ll need to create a dkim_selector file in the domain’s directory under domains.  The selector can be whatever you want (“mail” is common).  I like to use the machine’s hostname and today’s date.  This gives me the ability to easily rotate keys or change email servers while also keeping the old public keys available so that receiving servers can still verify older mail.

sudo echo $(hostname -s)$(date +'%Y%m%d') | sudo tee /etc/chasquid/domains/example.com/dkim_selector

Finally, you’ll need to create a new record on your domain’s DNS server with the contents of the dns.txt file.  The record should be a TXT record, the record name should be exampleYYYYMMDD._domainkey.example.com (substituting the selector name you created above and the proper domain name, of course) and the content should be the entire contents of the file.

Each time Chasquid receives an email message, it runs the post-data script (found in /etc/chasquid/hooks) to see what further processing it should be before delivering it to either Dovecot or to the intended recipient’s SMTP server.  The post data script that is included in the Debian distro is set up to automatically handle DKIM signing or validation (depending on whether it’s an outgoing or incoming message) if it can find the DKIM utilities we just compiled and a valid signing certificate (for outgoing mail), so no additional configuration is necessary.

TLS Certificates

Chasquid requires that TLS be enabled for clients submitting mail to be delivered, so we’ll first need to get a certificate. (Mail coming in from other servers does not strictly require TLS, but Chasquid has a nice feature that blocks mail coming in on a non-secured connection when it has previously seen mail from the same domain that is secured.  This isn’t a huge deal in my setup, since all of my mail first passes through my spam filtering service, MXGuarddog, so only their servers interact with mine, but it can help prevent some spam from getting in when outside servers are interfacing with Chasquid directly.)

While not strictly required, I’m using a separate certificate for each domain that I’m hosting.  This lets my users access their mailboxes using “mail.specificdomain.com” in their mail clients without needing to accept any invalid certificates and it makes for a seamless transition if I ever decide to split any domains off to different servers in the future.

I’m getting my certificates from Let’s Encrypt and I’m using the Lego ACME client to do it.  Chasquid is actually set up to interface nicely with Certbot, but because I’m using the Caddy webserver on this machine, and Caddy has it’s own ACME client, it would have been difficult (if not impossible) to get Certbot working correctly.  Lego can interface directly with my DNS servers (running PowerDNS), which is what prompted me to go that route.

Assuming you’re also using PDNS, before attempting to obtain a cert, you’ll need to ensure that the PowerDNS API is running on your DNS server (it is not on by default) and you’ll need your server’s API key.  Use caution in doing this, as an improperly set up API could let an attacker into your system. You can find instructions for enabling the API on the PowerDNS site.

Now that we’re ready to go, we’ll first install Lego and create a directory where the certificates will be stored.

sudo apt update
sudo apt install lego
sudo mkdir -p /etc/chasquid/.lego/certificates
sudo chmod 755 /etc/chasquid/.lego /etc/chasquid/.lego/certificates

Lego looks for PDNS_API_URL and PDNS_API_KEY environment variables to interface with the DNS server, so set these as appropriate.

PDNS_API_URL=https://dns.example.com/
PDNS_API_KEY=ENTER_API_KEY

With those set, we can request the certificate.  I use the singular ‘mail.example.com’ hostname to access all services, but if you’d prefer additional names, such as ‘smtp’, ‘pop3’, or ‘imap’, you can add them with additional -d parameters and they’ll also be added to the certificate.

sudo lego --dns pdns --accept-tos --email your.address@example.com --path /etc/chasquid/.lego -d mail.example.com run

Since Chasquid is expecting Certbot’s naming conventions, but Lego uses a different one, we’ll symlink the certificates to where Chasquid expects them to be:

sudo ln -s /etc/chasquid/.lego/certificates/mail.example.com.crt /home/chasquid/certs/mail.example.com/fullchain.pem
sudo ln -s /etc/chasquid/.lego/certificates/mail.example.com.key /home/chasquid/certs/mail.example.com/privkey.pem

To add the certs to Dovecot, we’ll create a config file in /etc/dovecot/domains that points to the certificate files.

cat << EOF | sudo tee /etc/dovecot/domains/mail.example.conf
local_name mail.$1 {
&nbsp; &nbsp; ssl_cert = </etc/chasquid/certs/example/fullchain.pem
&nbsp; &nbsp; ssl_key = </etc/chasquid/certs/example/privkey.pem
}
EOF

Finally, we’ll restart/reload the servers so that they’ll pick up the new configuration (reloading is generally preferred over a full restart, but Chasquid only has a restart option).

systemctl reload dovecot
systemctl restart chasquid

Creating Email Accounts

Now that the domain is set up, the only thing left to do is add users.  To do this, you’ll need two things: the desired email address and a password.

Passwords are stored as hashes in the /etc/chasquid/domains/example.com/passwd file.  The doveadm pw command can be used to hash the password with the Dovecot server’s preferred hashing method:

PASSWD=$(doveadm pw)

This will prompt for a password and return the hash, which will be stored in a shell variable named PASSWD.

To create the user account, append a line to the passwd file like so:

echo "user@example.com:$PASSWD:dovenull:dovenull:::::" >>/etc/chasquid/domains/example.com/passwd

The first field contains the username, which is the full email address; next is the password hash, as returned above; and ‘dovenull’ is the user and group that will “own” the user’s mailbox on the server.  The remaining fields are not needed for this specific setup, so we leave them empty, hence the ‘:::::’ at the end of the file.  Some additional features that could be added include the directory where mail should be placed (which would override the global value we set up in Part 1), mailbox quotas (which I’m not using), and network restrictions (to only allow access to the mailbox from certain subnets, for example), among other things.

Once the user is written to the file, they’ll be able to log in using their favorite email client and receive mail sent by other users.

MXGuarddog

In part one, I mentioned that I’m using a third-party spam filtering service, MXGuarddog, to filter my mail. All mail for my domains first gets delivered to them, they scan it, and then forward the legitimate mail on to my Chasquid server. In order to do this, they need to know the email addresses to accept.

At this point, if you’re using MXGuarddog or a similar service, don’t forget to set up the new account with the service provider.

MX Record

The final set in setting up a mail server is setting an MX, or mail exchanger, record in your domain’s DNS.  The MX record tells mail senders where to find the server that handles mail for your domain.

The MX record should have the fully qualified domain name that will be used for addressing mail as it’s name (example.com), and the name of the server that will receive it’s mail as the record’s contents (mail.example.com).  It should also have a priority which is used to determine the order in which servers are tried if there is more than one MX record.  If you only have one server, set the priority to 10.

If you’re using a third-party service like MXGuarddog to process your mail, be sure to use the MX record settings they provide to ensure you mail gets routed correctly.

Wrapping Up

We now have a fully functional mail server that can send, receive, and deliver messages for our users.  In the next post, I’ll cover the remaining loose ends of the setup, such as aliases and pipes, TLS certificate renewal, send-only email accounts (and why you might want them), and how to set up auxiliary services, like SpamAssassin or ClamAV.

Note: I may receive service credits from MXGuarddog for mentioning them here. Regardless, they’ve been a great provider who I’ve been paying a modest sum to manage my spam for years, and my recommendation is genuine.

Moving a Multi-Volume btrfs File System to a New Machine

A few years ago, I set up a “franken-NAS” using an Atomic Pi (a single-board computer running an Intel Atom processor that was originally intended to be used in some type of robot), a USB3 hard drive “toaster,” and a couple of WD Red hard drives. When I set it up, I decided to use btrfs in a RAID 1 (mirrored) configuration for redundancy. While it wasn’t super fast, it worked for the most part, though the board has always been a bit flaky. A while back, I bought a ZimaBoard to replace it, but until now, I hadn’t gotten around to doing it. The other day, however, I noticed that I couldn’t access the Atomic Pi machine anymore.

While the Atomic Pi has on-board eMMC storage, on which I installed the OS, it is only 16Gb and not enough for everything I was trying to do. Not wanting my home directories to be stored on the NAS hard drives (though I really had no reason not to), I added a SD card to the system and mounted it as my /home partition. As all SD cards tend to do, mine failed throwing the system into an unstable state.

I decided that now was the time to switch hardware, but how? I don’t have a ton of experience with btrfs and, while I found lots of tutorials for moving data to a new physical drive, I did not find much about moving drives between machines. Fortunately it wasn’t very difficult to figure out.

Note, while the ZimaBoard has two SATA ports on bard, I am still using the toaster for the time being, as I currently have an SSD plugged into one of the on-board ports. Since the board does have a PCIe slot, I may look into adding a multiport SATA card in the future, which would likely improve speeds.

Both systems are currently running Debian Bookworm.

Debian doesn’t have btrfs support installed by default, so first I needed to install it:

sudo apt update
sudo apt install btrfs-progs

Then I was able to run this command to find the filesystem:

sudo btrfs filesystem show

The output of this command looked like this. As you can see, the ZimaBoard found both drives and understood they were both part of the same firesystem.

Label: none  uuid: 34410f56-0d5b-4a25-b699-275d578c515b
    Total devices 2 FS bytes used 668.46GiB
    devid    1 size 9.10TiB used 670.01GiB path /dev/sdb
    devid    2 size 9.10TiB used 670.01GiB path /dev/sdc

If that doesn’t work, you can try btrfs device scan --all-devices, but my system found it right away, so I didn’t need to.

Now that we know that the system was able to find the btrfs volume, I need to create a directory for the mount point and mount the volume onto it (when I first set up the Atomic Pi I chose to use /external since these were “external,” USB-connected drives, so I’m sticking with that):

sudo mkdir /external
sudo mount /dev/sdb /external

Note, you can use the device path of any of the physical devices (/dev/sdc would have worked too). You should now be able to enter that directory and see the volume’s contents:

cd /external
ls -la

To have the volume mount automatically when the system boots, edit the /etc/fstab file:

vim /etc/fstab

and add the following line to the end of it:

UUID=34410f56-0d5b-4a25-b699-275d578c515b /external      btrfs   defaults,nofail 0   2

Here I’m using the volume’s UUID instead of the device path as it’s more durable (remember to change it to match the one returned for your system when you ran btrfs filesystem show). I’m using the nofail flag so that I can disconnect the drives and still boot up the machine without any issues if I ever need to do that for debugging or fixing issues.

So now I have my NAS moved over to better hardware. Of course, I’m not out of the woods quite yet as there’s still a handful of user accounts that need to be created, there’s probably a number of software packages that need to be installed, and I need to attempt to recover what I can from the corrupted SD card. But soon, hopefully, I’ll finally be up and running on the new hardware.

Setting Up a Mail Server with Chasquid and Dovecot – Part 1: The Servers

Conventional wisdom says you shouldn’t try to run your own mail server.  It’s difficult: as the percentage of email that is unwanted continues to grow, it becomes harder and harder to ensure that your legitimate messages are delivered.  It’s difficult to build a reputation as a legitimate sender, and the tools needed to do that are always changing.  You have to keep an eye on blacklists, and you need to be constantly vigilant about your server’s security to ensure that no else can hijack it to send spam…and that’s just for getting messages you send delivered.  On the receiving end there’s spam filtering, ensuring that incoming messages end up in the right mailbox, TLS certificate management…and the list goes on.

I guess I’m not conventional (or, perhaps I’m just not wise).  I’ve been running my own mail servers for the better part of 20 years now and it’s been working for me.  Of course, until now, I had never taken the time to really understand my mail servers because I was just using the ones installed with the various web hosting control panels that I’ve used over the years.  All of the control panels I’ve used, however, are built around the LAMP stack and, as my hosting needs have changed, with more things running in Docker or being built in other languages, like Go, I’ve kind of outgrown the panels.  I needed to find a new email solution and, after checking out several possible solutions, I settled on running my own servers using Chasquid and Dovecot.  I’ve been running this setup for a few months now and, so far, it is working well for me.

This article will be a tutorial on how I set up my servers for my specific needs.  I hope you find it useful, but honestly, like a lot of my tutorials, I’m writing it more for me to reference the next time I have to set it up again.  There are definitely things in here that I would do differently if the circumstances were different and there are probably things that won’t make sense in other situations.  The tutorial is built on these assumptions:

  • I’m using Debian Linux on a VPS with a large hosting provider.  At the time of this writing, I’m on Debian Bookworm (12.12).  The process will probably be similar for other Linux distros, but some paths and commands may be different.  You probably won’t have much luck if you try to run this from a server on your home network as ISPs tend to block SMTP ports on residential networks and receiving hosts tend to reject mail coming from residential networks, both in the name of fighting spam.  Server specs shouldn’t matter too much if your email traffic is light to moderate.
  • Both the SMTP server (Chasquid) and POP/IMAP server (Dovecot) will be running on the same machine.
  • Each domain will have a single hostname (mail.example.com) that will be used for both sending and receiving mail.
  • Users are required to use TLS when connecting to the servers to send or receive mail.  Both Chasquid and Dovecot will be configured with TLS certificates for each domain that they host.
  • I’m using a third-party service (MXGuarddog) for spam filtering, so I am not installing SpamAssassin, ClamAV, Greylistd or any other spam/virus filtering or prevention tools on my server.  Chasquid does, however, support those three aforementioned tools.
  • Since I am using MXGuarddog, I’m using an arbitrary port number for incoming SMTP connections.  MXGuarddog knows the port I’m using and routes the mail it processes there instead of to port 25, but if spammers try to sneak around the protections I have in place, they won’t be able to find the server.

The setup process isn’t difficult, but it is lengthy, so I’ve decided to break up the tutorial into three parts: this article will focus on getting the services installed and running; part two will look at configuring domains and adding email accounts, including setting up DKIM signing and individual TLS certificates, and part three will cover the extra things that might be important, including aliases, certificate renewals, send-only accounts, and how Chasquid can interface with other services, such as spam filters.

Step 1: Installing Dovecot

If you’re familiar with Linux mail servers, you’ve probably heard of Dovecot.  Taking it’s name from a house for pigeons or doves (appropriate as pigeons have traditionally been used as messengers), it is currently one of the most popular POP3 and IMAP4 mail servers for Linux.

Dovecot is a complex piece of software that can do a lot of different things.  For me, I only need a POP/IMAP server for delivering mail to my email clients.  I’m also running Dovecot’s LMTP (local mail transfer protocol) server, which is what Chasquid will use to deliver incoming messages to their respective mailboxes.

To get started we’ll first make sure our apt package listing is up to date:

sudo apt update

Then we’ll install the necessary Dovecot packages.  This command will install a bunch of additional dependencies as well:

sudo apt install dovecot-imapd dovecot-pop3d dovecot-lmtpd

Now we need to configure Dovecot.  Dovecot comes with an elaborate, multi-file configuration that tries to address every possible configuration option.  I found that it was much easier to replace that setup with a custom configuration than to try overriding the original files.

First, we’ll rename the old dovecot.conf file, just in case we ever need to go back and check something in it:

sudo mv /etc/dovecot/dovecot.conf /etc/dovecot/doevcot.conf.bak

Then we’ll create a new dovecot.conf file with our custom configuration with this command:

cat <<EOF | sudo tee /etc/dovecot/dovecot.conf
#
# Logging
#
log_path = /var/log/dovecot/dovecot.log

#
# Email storage
#

# Store emails in /var/vmail/domain/user
mail_home = /var/vmail/%d/%n

# Store mailboxes in maildir format (file per message).
mail_location = maildir:~/Maildir

# User and group used to store and access mailboxes.
mail_uid = dovecot
mail_gid = dovecot

# As we're using virtual mailboxes, the system user will be "dovecot", which
# has uid in the 100-500 range. By default using uids <500 is blocked, so we
# need to explicitly lower the value to allow storage of mail as "dovecot".
first_valid_uid = 100
first_valid_gid = 100

#
# Authentication
#

# For convenience, place all mail account config together in the
# chasquid domain directory. Since chasquid uses "users" for it's
# user directory, an extra file named "passwd" shouldn't cause conflicts.

auth_mechanisms = plain
passdb {
    driver = passwd-file
    args = scheme=CRYPT username_format=%u /etc/chasquid/domains/%d/passwd
}
userdb {
    driver = passwd-file
    args = username_format=%u /etc/chasquid/domains/%d/passwd
}

#
# TLS
#

# TLS is mandatory.
# Add a conf file for each domain with paths to TLS certs
# in /etc/dovecot/domains
ssl = required
!include_try /etc/dovecot/domains/*.conf

# Only allow TLS 1.2 and up.
ssl_min_protocol = TLSv1.2

#
# Protocols
#
protocols = lmtp imap pop3

#
# IMAP
#
service imap-login {
    inet_listener imap {
        # Disable plain text IMAP, just in case.
        port = 0
    }
    inet_listener imaps {
        port = 993
        ssl = yes
    }
}

service imap {
}

#
# POP3
#
service pop3-login {
    inet_listener pop3 {
        # Disable plain text POP3, just in case.
        port = 0
    }
    inet_listener pop3s {
        port = 995
        ssl = yes
    }
}

service pop3 {
}

#
# Internal services
#
service auth {
    unix_listener auth-userdb {
    }

    # Grant chasquid access to request user authentication.
    unix_listener auth-chasquid-userdb {
        mode = 0660
        user = chasquid
    }
    unix_listener auth-chasquid-client {
        mode = 0660
        user = chasquid
    }
}
service auth-worker {
}
dict {
}
service lmtp {
    # This is used by mda-lmtp.
    unix_listener lmtp {
    }
}

#
# Default Folders
#
namespace inbox {
    type = private
    separator = .
    inbox = yes
    mailbox Drafts {
        special_use = \Drafts
        auto = subscribe
    }

    mailbox Junk {
        special_use = \Junk
        auto = create
    }

    mailbox "Junk E-mail" {
        special_use = \Junk
        auto = no
    }

    mailbox spam {
        special_use = \Junk
        auto = no
    }

    mailbox Spam {
        special_use = \Junk
        auto = no
    }

    mailbox Trash {
        special_use = \Trash
        auto = subscribe
    }

    mailbox TRASH {
        special_use = \Trash
        auto = no
    }

    mailbox "Deleted Items" {
        special_use = \Trash
        auto = no
    }

    mailbox Sent {
        special_use = \Sent
        auto = subscribe
    }

    mailbox "Sent Mail" {
        special_use = \Sent
        auto = no
    }

    mailbox "Sent Messages" {
        special_use = \Sent
        auto = no
    }

    mailbox "Sent Items" {
        special_use = \Sent
        auto = no
    }

    mailbox sent-mail {
        special_use = \Sent
        auto = no
    }

    Archive {
        special_use = \Archive
        auto = create
    }

    mailbox Archives {
        special_use = \Archive
        auto = no
    }
}
EOF

This configuration does a bunch of things:

  • It sets up logging, to help with debugging.
  • It specifies that we’re using virtual accounts (accounts that are not tied to Linux user accounts) and sets the user that will own those accounts.
  • It sets where mailboxes will be stored (by domain and within /var/vmail) and how they’ll be set up (using Maildir format, for one file per message).  It also specifies how user accounts are defined and where passwords are stored (I’m storing them in per-domain passwd files alongside the Chasquid domain configurations, to keep all user-related configs together.)
  • It defines our TLS requirements and specifies where to find domain-specific TLS certs (which we’ll cover more in part 2).
  • It specifies which protocols Dovecot will handle and configures the specifics of those.  Most notably, we’re disabling the older non-encrypted versions of the POP3 and IMAP protocols and requiring the use of TLS to fetch mail for better security.
  • It establishes an interface that Chasquid can use to authenticate users so that they can use a single password to both send and receive mail.
  • And it defines a what a default mailbox should look like when a user first accesses it.  As part of this, it aliases commonly used folder names together so that, for example, all sent mail will stay together across different email clients, even if one uses “Sent,” another uses “Sent Items,” and a third uses “Sent Mail.”

We also need to set up a couple of other directories.  First, we’ll create /etc/dovecot/domains where we’ll add a config file for each domain we host.  This will contain paths to the domain’s TLS certificates so that users can access their mailboxes using mail.example.com rather than myserver.example.com and not get certificate warnings.  Then we’ll add directories for dovecot to write it’s log files and for it to store the mail it receives.  Finally, we’ll set ownership on the mailbox directory to the dovenull user.

sudo mkdir -p /etc/dovecot/domains
sudo mkdir -p /var/log/dovecot
sudo mkdir -p /var/vmail
sudo chown dovenull.dovenull /var/vmail

Finally, we’ll need to restart dovecot so that it will start to use the new configuration:

sudo systemctl restart dovecot

If all goes well, Dovecot is configured and running.  Now it’s time to install Chasquid.

Installing Chasquid

I’ll admit I had not heard of Chasquid until I started researching this project, but it looked interesting, so I decided to give it a try and I’ve been happy.  Chasquid is a security-first MTA (mail transfer agent) that mostly works out of the box.

Chasquid takes its name from the Chasquis, a relay team of highly trained messengers that delivered oral messages throughout the Inca empire.  It has several features that I liked, such as:

  • It does not support features like open relaying, that are often lead to improper, insecure mailserver configurations.
  • It requires TLS for users submitting outgoing messages.
  • It can handle all of the standard sender validation techniques, including SPF checking and DKIM signing and verification.
  • It keeps track of the domains it receives incoming messages from and will reject connections if the domain’s sending server had previously connected with TLS but later tries to use plain text.  This helps prevent spammers from spoofing legitimate servers or using mailware to send messages.

Chasquid also supports all of the features you expect from an SMTP server:

  • The ability to scan for spam or viruses using third party tools like SpamAssassin or ClamAV, as well as greylisting.
  • Email aliases, including aliases that “pipe” to commands for special processing.
  • Suffix dropping (i.e. the ability to use user+somethingextra@example.com and have it be delivered to user@example.com automatically).

Chasquid is available as a Debian package, so we can install it like any other package:

sudo apt install chasquid

Then we can configure it with this command:

cat <<EOF | sudo tee -a /etc/chasquid/chasquid.conf

# Deliver email via lmtp to dovecot.
mail_delivery_agent_bin: "/usr/bin/mda-lmtp"
mail_delivery_agent_args: "--addr"
mail_delivery_agent_args: "/run/dovecot/lmtp"
mail_delivery_agent_args: "-f"
mail_delivery_agent_args: "%from%"
mail_delivery_agent_args: "-d"
mail_delivery_agent_args: "%to%"

# Use dovecot authentication.
dovecot_auth: true

# Log to file
mail_log_path: "/var/log/chasquid/chasquid.log"
EOF

Chasquid uses “sane defaults,” so there isn’t much configuration required.  The main thing we do here is tell Chasquid how to access Dovecot’s LMTP service to deliver mail.  We also set up logging and tell Chasquid to use Dovecot authentication to validate users trying to send mail against their Dovecot accounts.  All that’s required to do this is `dovecot_auth: true`, as Chasquid will look in the standard locations where most Linux distributions place this socket when it tries to connect.  If you’re using an uncommon Linux distro or a non-starndard Dovecot installation, you might have to make adjustments.

As I noted in the intro, I’m using the third-party provider, MXGuarddog, as my spam filter.  MXGuarddog serves as the mail exchanger (MX) for my domains.  Sending MTAs forward all mail to their servers, they filter it, and forward non-spam on to Chasquid.  Since there’s nothing stopping a spammer from ignoring my MX records and just trying to connect directly to any of my domains’ known host names, like mail.example.com, I use an arbitrary port number for server-to-server SMTP:

cat <<EOF | sudo tee -a /etc/chasquid/chasquid.conf

# since we're using MXGuardDog as our MX, listen on a non-standard
# port so that non-compliant spammers won't find us
smtp_address: ":20005"
EOF

Of course, it’s even better to change the port number to something completely arbitrary, like 59731 or 20956.  Once you’ve done that, you can set your server’s port number in MXGuarddog’s settings, and they’ll forward mail to that port instead of port 25.  As an alternative, you could set your server’s firewall to only allow traffic in from MXGuarddog’s published IP addresses.

I’ve been using MXGuarddog for years and I’ve found that their service works much better than any SpamAssassin config I’ve ever tried, and at 25 cents per email account per month, it’s totally worth it.  My only complaint with MXGuarddog is that they tend to be a little too aggressive:  even with my settings at their most permissive levels, they still block a lot of mail I want, especially from mailing lists, so it’s important to keep an eye on your spam reports and whitelist anything important that gets flagged.

If you choose not to use an outside spam filter and choose instead to use SpamAssassin, ClamAV, and/or Greylistd to control spam, there’s no further configuration needed for Chasquid.  Chasquid runs a hook script for every message it processes and, if it finds any of those programs installed on the server, it will call them during processing.

That’s all that’s needed to configure Chasquid, so we can give it a restart to be ready to start sending and receiving mail:

systemctl restart chasquid

Setting up a firewall

It’s always good to run a firewall on your server as an extra layer of defense against attacks.  If you already have a firewall configured, you’ll want to make sure that you open the following ports in it:

  • Dovecot: 993 (secure IMAP), 995 (secure POP3)
  • Chasquid: 587 (secure SMTP with STARTTLS), 465 (secure SMTP), and either 25 or whatever alternate port you chose for incoming SMTP above

You should not open  ports 143 (non-encrypted IMAP) or 110 (non-encrypted POP3), as these protocols pass credentials and message contents in the clear, and we specifically turned them off in the Dovecot configuration.

If you don’t have a firewall on your server already, I personally like to use ufw (Uncomplicated Firewall) for its ease of use.  To install ufw run:

sudo apt install ufw

The set up your configuration using the ufw command.

First, make sure you open up SSH (port 22), since that’s most likely what you’re using to access the server right now, and you don’t want to block yourself:

sudo ufw allow 22/tcp

Then allow the ports for Dovecot and Chasquid (remember to change 25 to your random SMTP port if you changed it):

sudo ufw allow 993,995/tcp
sudo ufw allow 25,465,587/tcp

And finally, enable the firewall.  This should work without dropping your current SSH session.

sudo ufw enable

Wrapping up

So far we’ve installed Dovecot and Chasquid using a configuration that’s secure and that should make sense for most users.  In part two, we’ll look at how to add email domains to the server, how to set up user email accounts, and we’ll explore what needs to be done to ensure the messages we send get delivered properly.

Note: I may receive service credits from MXGuarddog for mentioning them here. Regardless, they’ve been a great provider who I’ve been paying a modest sum to manage my spam for years, and my recommendation is genuine.

Am I an Avid Reader Yet?

At the beginning of 2024, I gave myself a goal to read more. As a kid, I loved to read, but somewhere along the line, life happened, and aside from an occasional book here and there, I hadn’t really been reading much for several years. I decided to change that.

I started off by making a list of books that I remembered having to read in high school and college. I thought it would be fun, or at least interesting, to read them again with a fresh set of eyes. Back then, my perspective was to read what I needed to know to be successful in class, not so much to enjoy the story. Some books I liked, some books I didn’t, but I wanted to give them a fresh look with my now more experienced eyes. What would I get from them twenty or more years later that I missed the first time? Would my expanded world view make me look at the stories differently than I did as a kid? Would I find some new meaning in a book that I hated as a teen?

It started on a Saturday afternoon with a digital copy of The Great Gatsby that I checked out from the New York Public Library’s app. To my surprise, I was finished with it by the end of the day.

I had no idea how many books I could realistically finish, so I set my goal relatively low: five books by the end of the year. After Gatsby, I went on to read several other American classics such as A Separate Peace, To Kill A Mockingbird, The Red Badge of Courage, and Tortilla Flat. By July I had reached my goal.

On Prime Day, I decided to buy a Kindle. Until then, I was mostly reading on an older Samsung Galaxy Tab tablet. It worked well, as long as I wasn’t trying to read outside in the sun. I figured the Kindle would be more versatile.

The Kindle came with a trial subscription to Kindle Unlimited. My initial impression of the service was that it didn’t have a lot of what I wanted to read. Still, I figured I’d find things that looked interesting for the three months that I had the trial and then I’d cancel. Of course, I still keep finding things I want to read, so I still have the subscription which I am now paying for (but I guess I’m using it enough to make it worthwhile).

By the end of 2024, I had finished 13 books, with another two in progress. I decided that a modest increase to 15 books would be a good goal for 2025, and I kept chugging away.

As of today, April 22, 2025, I have finished my 15th book of the year, Dr. Suess Goes to War: The World War II Editorial Cartoons of Theodore Suess Geisel, with still over eight moths left to go in the year.

I usually have two books going at once, generally one fiction and one nonfiction at any given time, and I try to dedicate about and hour a day to reading. Some days that doesn’t happen and I only end up reading a few pages, some days I get really into it and read for much longer. As of today, Amazon is reporting that I’ve read for 150 days in a row!

So what do you think? Am I qualified to call myself an avid reader yet?

<