Simple and minimal SMTP server
1. Introduction
Web applications usually need to send email notifications. For example Moodle needs to notify students and teachers about various events. Without being able to send notifications, Moodle and many other web applications loose half of their usefulness. On many web applications you cannot even finish the registration process and cannot login, unless you verify your email address (the application sends you an email with a link that you have to click).
To send emails, an application needs to contact by SMTP a local or remote mail server. Installing a mail server locally is not so easy because it needs also some DNS and other configurations. Otherwise the mails that are sent will end up being classified as spam and most probably will not reach the recipient.
This article describes how to install your own mail server and how to configure it properly. The aim of this mail server is not to be a full-fledged system, where users can have accounts and use it daily, but just to support web applications (like Moodle) with sending notifications.
For security resons, it allows only some trusted hosts to send emails. This prevents any spammers from abusing it. It also does not have any local accounts and does not accept any emails, making it simpler — no need for antispam and antivirus software, no need for POP and IMAP, etc. It is just a simple and minimal SMTP server, which can be used by (trusted) web applications to send notification emails to their users.
2. Minimal DNS configuration
In order to build a mail server you need to own a domain (say
example.org
) and be able to customize its DNS records.
For each email domain you need something like this on the DNS configuration:
smtp.example.org. IN A 10.11.12.13 example.org. IN MX 10 smtp.example.org. example.org. IN MX 20 smtp.example.org. example.org. IN TXT "v=spf1 mx -all"
Yes, both MX records point to the same server, it is not a typo. It is done because the configuration of the server enables postscreen deep protocol tests in order to block spambots (for more details see this article). This means that the server disconnects new/unknown SMTP clients when they connect for the first time, even if they are legitimate. A legitimate client usually goes on to try the second mailserver in the list, and the second time they try to connect they are accepted. |
The last line basically tells to the other SMTP servers that only this
server is allowed to send emails on behalf of this domain, and no other
servers. This is done to prevent spammers from faking your email
addresses. If a spammer tries to send a mail as if it is coming from
your domain, the SMTP server that is getting this email will consult
this DNS record and will figure out that the server of the spammer is
not allowed to send emails on behalf of example.org
.
Depending on your DNS server, DNS changes may take from a few minutes to
a couple of days to propagate. You can use dig
to verify that these
DNS records have been activated:
user@host:~$ dig MX example.org +short 10 smtp.example.org. 20 smtp.example.org. user@host:~$ dig A smtp.example.org +short 10.11.12.13 user@host:~$ dig TXT example.org +short "v=spf1 mx -all"
3. Build a postfix container
We assume that we have already installed docker-scripts and revproxy. Now let’s install a postfix container:
ds pull postfix
ds init postfix @smtp.example.org
cd /var/ds/smtp.example.org/
vim settings.sh (1)
ds make
1 | Make sure to set proper values to HOSTNAME and
VIRTUAL_DOMAINS . |
Check the installation:
cd /var/ds/smtp.example.org/
ls sslcert/
ls /etc/cron.d/
ls config/
cat config/trusted_hosts
cat config/virtual_alias_maps
cat config/virtual_alias_maps.regexp
ls config/dkim-keys/
ds shell
ps ax
systemctl status postfix
ls /etc/postfix/
postconf -nf | less
tail /var/log/mail.log
exit
4. Make the mail server trustworthy
To increase the chances that the other mail servers take seriously the emails comming from our mailserver we have to do some extra DNS configurations. Otherwise the mails that are sent may end up being classified as spam and most probably will not reach the recipient.
4.1. Activate a DKIM key
DKIM keys are used by a mail server to sign the emails that it sends, so that the emails cannot be changed in transit, and so that the receiver can verify that this server is authorized to send emails for a domain. It is an important tool against spams and faked emails. If an smtp server signs the messages that it sends, it is less likely that they will be classified as spam.
Installation scripts generate a DKIM key as well, which is on
config/dkim-keys/example.org/
. To activate it you need to add
a record like this on the DNS configuration of the domain:
mail._domainkey.example.org. IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQE....kMJdAwIDAQAB" )
You can find the content of the public key on the file:
config/dkim-keys/example.org/mail.txt
.
To check whether it has been activated or not, try the command:
dig TXT mail._domainkey.example.org +short
4.2. Create a DMARC record
DMARC is a standard that allows you to set policies on who can send email for your domain based on DKIM and SPF.
You can add a DMARC Record on DNS that will allow you to get weekly reports from major ISPs about the usage of your email domain.
-
Go to http://dmarc.postmarkapp.com/ and add your email address where you want to receive reports, and email domain name (
example.org
). -
On the DNS configuration of the domain add a TXT record like this:
_dmarc.example.org. IN TXT ( "v=DMARC1; p=none; pct=100; " "rua=mailto:re+x2i0yw1hoq7@dmarc.postmarkapp.com; " "sp=none; aspf=r;" )
The value of this TXT record is the one generated by the website above.
-
To check that it has been activated, try the command:
dig TXT _dmarc.example.org. +short
5. Test the SMTP server
For a quick automated test of the basic functionality of the server
try: ds test1
. The following tests can be done manually.
5.1. Send from inside
Let’s send some test emails from inside the container.
cd /var/ds/smtp.example.org/
ds shell
swaks --from noreply@example.org --to info@example.org
tail /var/log/mail.log
cat /host/config/virtual_alias_maps
cat /host/config/virtual_alias_maps.regexp
This test succeeds because the recipient (info@example.org
)
matches the line /^info@/
on the
virtual_alias_maps.regexp
. The sender can be anything (in this
case noreply
) and the email will still be sent, since we are sending
from localhost, which is a trusted host.
This email is actually forwarded to the address that is listed on
virtual_alias_maps.regexp
(check also the spam folder if you
cannot find it on the inbox).
swaks --from info@example.org --to noreply@example.org
tail /var/log/mail.log
This test fails because the recipient (noreply@example.org
) does
not match any entry on virtual_alias_maps
or
virtual_alias_maps.regexp
, so the mailserver does not know
what to do with this email.
5.2. Send to gmail
Let’s send test emails to a gmail account:
swaks --from info@example.org --to <username>@gmail.com -tlso
tail /var/log/mail.log
swaks --from noreply@example.org --to <username>@gmail.com -tlso
tail /var/log/mail.log
Both of these tests will succeed. The option -tlso
enables the
TLS encryption of the connection.
On gmail use "Show original" from the menu, to see the source of the received email.
You can also send a reply from <username>@gmail.com to
info , but you cannot send to noreply .
|
5.3. Send from the host
Let’s try to send a test email from the host (outside the container):
cd /var/ds/smtp.example.org/
apt install swaks
swaks --from info@example.org --to admin@example.org -tlso
ds exec tail /var/log/mail.log
It may fail, because the IP of the host might not be on the list of
the trusted hosts. Append it to config/trusted_hosts
and run
ds inject update.sh
. Then verify that now it works:
echo '10.11.12.13' >> config/trusted_hosts
ds inject update.sh
swaks --from info@example.org --to admin@example.org -tlso
ds exec tail /var/log/mail.log
5.4. Add a new address
Try to send an email to test1@example.org
:
swaks --from info@example.org --to test1@example.org -tlso
... <** 550 5.1.1 <test1@example.org>: Recipient address rejected: User unknown in virtual alias table ...
It will fail because the recipient does not exist on the alias
table. On config/virtual_alias_maps
add a line like this:
test1@example.org <username>@gmail.com
Then update the alias db and verify that now you can send email to this address:
#ds inject update.sh
ds exec postmap /host/config/virtual_alias_maps
swaks --from info@example.org --to test1@example.org -tlso
ds exec tail /var/log/mail.log
5.5. Send to ports 587
/465
swaks --server smtp.example.org:587 \
--from info@example.org --to admin@example.org
swaks --server smtp.example.org --port 465 \
--from info@example.org --to admin@example.org
On the host these should work. However, make sure that they fail if you try them from another server. If they don’t fail, then everyone can use your SMTP server to send emails, and this is wrong. Something is wrong with the configuration of the server. |
To allow a server to send emails from your SMTP, you should append its IP to the list of the trusted hosts:
|
6. Check the health of the mail server
-
Send an email to
check-auth@verifier.port25.com
:swaks --server smtp.example.org -tlso \ --from info@example.org \ --to check-auth@verifier.port25.com
The automatic reply will give you important information about the status and health of your email server (for example whether the mails sent from it pass the SPF and DKIM checks, whether they are considered spam or not, etc.)
-
Go to https://www.mail-tester.com/ and send a message to the email address displayed there, like this:
swaks --server smtp.example.org -tlso \ --from info@example.org \ --to test-1p4f6@mail-tester.com
Then click the button for checking the score.
-
There are lots of other tools and websites that help to check the configuration of a mail server (DNS settings, configuration, security features, etc.) For example:
7. Add another email domain
This smtp server can support more than one mail domains. If we want to
add another mail domain, for example example.com
, we have to do these:
-
Set DNS configurations like this:
; mail for example.com smtp.example.com. IN CNAME smtp.example.org. example.com. IN MX 10 smtp.example.com. example.com. IN MX 20 smtp.example.com. example.com. IN TXT "v=spf1 mx -all"
Note that the first record (
CNAME
) redirects the new smtp domain to the first one.You can check these DNS configurations like this:
dig +short smtp.example.com. A dig +short example.com. MX dig +short example.com. TXT
-
Edit
settings.sh
and add the new domain atVIRTUAL_DOMAINS
:VIRTUAL_DOMAINS=' example.org example.com '
-
Run
ds make
to rebuild the container. The rebuild is done because we need to request a new SSL cert from letsencrypt, which covers all the domains (including the new one). -
Go to http://dmarc.postmarkapp.com/ and generate a DMARC record for the new domain.
-
Update the DNS configuration with records like these:
mail._domainkey.example.com. IN TXT ( "v=DKIM1; h=sha256; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQE....kMJdAwIDAQAB" ) _dmarc.example.com. IN TXT ( "v=DMARC1; p=none; pct=100; " "rua=mailto:re+x2i0yw1hoq7@dmarc.postmarkapp.com; sp=none; aspf=r;" )
Note that:
-
The value of the key for the DKIM record can be found on the file:
config/dkim-keys/example.com/mail.txt
-
The value of the DMARC record is the one obtained on the previous step.
You can check them like this:
dig +short mail._domainkey.example.com. TXT dig +short _dmarc.example.com. TXT
-
8. Relay domains
If another mail server is installed on the same machine where this
SMTP server is installed, there is a problem, because they both need
to use the port 25
for receiving mails. SMTP ports cannot be
shared by more than one application, and it is not possible to proxy
them (like HTTP or HTTPS ports, for example).
A workaround in this case would be to use this simple SMTP server as a relay for the mail domains that are served by the other mail server. It works like this:
-
When a connection comes on the port
25
(someone is trying to send an email), it is handled by the simple SMTP server, since this port is forwarded to this server. -
If the domain of the receipient’s address is one of the domains that is managed by the simple SMTP server, the mail is handled by this server itself.
-
Otherwise, if the domain is one of those that are managed by the other mail server, it is relayed instead to that mail server.
In other words, the simple SMTP server behaves like a kind of router for the emails that are received, based on the email domain of the receipent’s address.
Let’s see how to implement this SMTP relay. We will assume that the
domains that are managed by the other mail server are example1.org
,
example2.org
and example3.org
. We can create the custom command
ds relay-setup
like this:
cd /var/ds/smtp.example.org/
mkdir -p cmd
cat <<'__EOF__' > cmd/relay-setup.sh
cmd_relay-setup() {
# create a config file for relay_domains
cat <<EOF > config/relay_domains
example1.org
example2.org
example3.org
EOF
# create a config file for transport_maps
local smtp_relay='smtp:10.214.77.201:25' (1)
cat <<EOF > config/transport_maps
example1.org $smtp_relay
example2.org $smtp_relay
example3.org $smtp_relay
EOF
# setup transport_maps
ds exec postconf -e 'transport_maps=hash:/host/config/transport_maps'
ds exec postmap /host/config/transport_maps
# setup relay_domains
ds exec postconf -e relay_domains=/host/config/relay_domains
# reload postfix configuration
ds exec postfix reload
}
__EOF__
cat cmd/relay-setup.sh
1 | We assume that 10.214.77.201 is the internal (fixed) IP of the
other mailserver. |
In order to call this command automatically whenever we rebuild the
container, we can override the command ds config
like this:
cat <<'EOF' > cmd/config.sh
rename_function cmd_config standard_config
cmd_config() {
standard_config
ds relay-setup
}
EOF
cat cmd/config.sh