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.