I have a local mail server with Postfix and Dovecot — I use it as a local SMTP smart host, and “notification center”.
While gathering information for this post; I discovered that my file server have been sending me emails, warning me of S.M.A.R.T. errors on a disk in the ZFS pool. Every day — since September!
So I’ve included a section on how to avoid missing important server emails.
Table of contents
The setup
There are several advantages with having a local smart host; it will handle authentication and encryption for upstream SMTP servers, so local clients don’t have to. This makes it a easy to send emails from simple devices that often have limited configuration options for email.
Switching upstream SMTP server only requires configuration changes on the smart host. I’m currently using my ISP SMTP server. But I’ve also used AWS SES and Mailgun in the past.
By setting up IMAP and a local email account; I’m using the server as a “notification center”. I like getting email about failing cron jobs, and other warnings and notifications, but not in my primary inbox. Instead they get delivered locally, and I use NeoMutt to read them 🙂
Postfix
First things first — installing Postfix:
$ sudo apt-get install postfix
Let’s have a look at the configuration in /etc/postfix/main.cf
, here are the important lines:
myhostname = mail.lan.uctrl.net
myorigin = /etc/mailname
mydestination = $myhostname, localhost.lan.uctrl.net, localhost, mail.public-domain.net
relayhost = [smtp.altibox.no]
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 192.168.1.0/24
myhostname
: Host name of this mail systemmyorigin
: Domain name that locally-posted mail appears to come frommydestination
: List of domains that are delivered locallyrelayhost
: The next-hop destination for non-local mailmynetworks
: Clients allowed to relay mail
Looking at /etc/mailname
, we can see that it also contains our host name:
mail.lan.uctrl.net
So mail sent from this machine will come from user@mail.lan.uctrl.net
Next — let’s look at /etc/aliases
:
postmaster: root
root: hebron
hebron: thomas
newsletter: :include:/etc/mail/newsletter.list
This lists aliases for local users, but can also forward mail to external addresses, or contain lists. In the file above; mail sent to postmaster
, root
, or hebron
— will be delivered to the local user thomas
. Which we will set up later.
The newsletter list, simply contains email addresses:
user1@example.com
user2@example.com
If we do any changes to the alias file, we will need to initialize the database:
$ sudo newaliases
In my configuring — I’m just using the ISP provided SMTP server, which doesn’t require any authentication. More configuration is needed if the SMTP server uses authentication and encryption. I’ve explained how to integrate Amazon SES with Postfix in a previous post.
Right — now that we have configured Postfix, let’s restart it and send a test mail:
$ sudo systemctl reload postfix
$ sudo apt install mailutil
$ echo "Mail body" | mail -s "Mail subject" thomas@mydomain.net
To verify that Postfix is accepting connections on post 25, you can use telnet:
$ telnet mail.lan.uctrl.net 25
Trying 192.168.1.x...
Connected to mail.lan.uctrl.net.
Escape character is '^]'.
220 mail.lan.uctrl.net ESMTP Postfix (Ubuntu)
mydestination
, port 25 forwarded to the mail server, and an MX record for the public domain — you can receive external emails on your server.
Generic maps
We now have a local SMTP server that will receive local emails, and send non-local emails to the next SMTP server. But there is a problem, local email addresses like user@localdomain
isn’t ideal when sending to external addresses. It looks weird and the email might get rejected. We need to rewrite the sender — enter generic maps.
Optional lookup tables that perform address rewriting in the Postfix SMTP client — Postfix manual
In our /etc/postfix/main.cf
file, we add the following line:
smtp_generic_maps = regexp:/etc/postfix/generic_maps
My generic map file looks like this:
/^(.*)@(.*)\.lan\.uctrl\.net$/ thomas+${1}.${2}@mail.public-domain.net
For the changes to take effect, we need to reload Postfix:
$ sudo systemctl reload postfix
Using regex; it rewrites the email sender address:
user@host.lan.uctrl.net --> thomas+user.host@mail.public-domain.net
The sender address is now publicly valid, but the local origin is not lost 🙂
You may need to add or update your SPF record, to allow your upstream SMTP server to send emails for your public domain.
Using the Altibox SMTP server, I added the following SPF record to my mail.public-domain.net
domain:
v=spf1 include:_spfsoft.services.altibox.net ~all
Header checks
As I wrote in the introduction — I’m using locally delivered emails for server notifications. But some emails are so important, that I do want them delivered to my main inbox. And for that we can use header checks:
Postfix built-in content inspection — Postfix manual
BCC user@domain
: Add the specified address as a BCC recipientREDIRECT user@domain
: Write a message redirection request to the queue file
In our /etc/postfix/main.cf
file, we add the following line:
header_checks = regexp:/etc/postfix/header_checks
My header check files looks like this:
/^Subject: SMART error*/
BCC thomas@mydomain.net
For the changes to take effect, we need to reload Postfix:
$ sudo systemctl reload postfix
Now all emails where the subject starts with SMART error
, will be blind copied to my main inbox 👍
Dovecot
With Postfix all configured — it’s time for the IMAP server; Dovecot:
$ sudo apt install dovecot-imapd
After installing it — we need to enable the IMAP protocol. /usr/share/dovecot/protocols.d/imapd.protocol
should look like:
protocols = $protocols imap
Let’s restart Dovecot and test it:
$ sudo systemctl restart dovecot.service
$ telnet localhost imap
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
* OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ STARTTLS AUTH=PLAIN] Dovecot (Ubuntu) ready.
/etc/dovecot/conf.d/10-ssl.conf
, and enable the imaps
protocol. I’m only using mine locally, so I haven’t set up encryption.
Lastly we need to create the user that will receive the local emails:
$ sudo useradd --create-home -s /sbin/nologin thomas
$ sudo passwd thomas
Now we can log into IMAP with the user thomas
and our chosen password 👍
thomas@localdomain
to work — the local domain must have an MX DNS record pointing to the email server, or an A record pointing to the receiving host.
Ansible
Our local SMTP/IMAP server is ready. But we need to instruct all other clients to use this smart host when sending email — one easy way to do that is with Ansible.
Folder structure:
.
├── postfix.yml
└── roles
└── postfix
├── defaults
│ └── main.yml
├── handlers
│ └── main.yml
├── tasks
│ └── main.yml
└── templates
└── aliases
postfix.yml
We don’t want to run this playbook on the mail server itself, so we exclude it.
- hosts: all !mail.lan.uctrl.net
vars:
postfix_conf:
relayhost: "[mail.lan.uctrl.net]"
myhostname: "{{ inventory_hostname }}"
myorigin: "$myhostname"
mydestination: "$myhostname, localhost"
ansible_python_interpreter: /usr/bin/python3
roles:
- postfix
defaults/main.yml
---
# Postfix configuration dictionary, e.g.:
# postfix_conf:
# relay_domains: "$mydestination"
# relay_host: "example.com"
#
postfix_conf: {}
# Whether to run 'postfix check' before it's started
postfix_check: true
handlers/main.yml
---
- name: check postfix
become: true
command: postfix check
when: postfix_check
listen: check restart postfix
- name: restart postfix
become: true
service: name=postfix state=restarted
listen: check restart postfix
tasks/main.yml
---
- name: Install Postfix
become: true
package: name=postfix state=latest
- name: Enable Postfix
become: true
service: name=postfix state=started enabled=yes
- name: Install Mailutils
become: true
package: name=mailutils state=latest
- name: Copy aliases
become: true
template:
src: "aliases"
dest: "/etc/aliases"
- name: Generate new alises
become: true
command: newaliases
- name: Configure Postfix
become: true
command: "postconf -e \"{{ item.key }}={{ item.value }}\""
ignore_errors: true
notify: check restart postfix
with_dict: "{{ postfix_conf }}"
templates/aliases
# See man 5 aliases for format
postmaster: root
root: hebron
hebron: thomas@mail.lan.uctrl.net
Wrapping it up
I always configure email delivery on my servers and containers, and set up forwarding to the local account on the mail server. It’s just easier to have everything in one place. Obviously I need to check this inbox more often, as I had missed important warnings of a failing disk 😛
Playing with emails locally is also a good way to learn about the workings of email 👍
Last commit 2024-11-11, with message: Add lots of tags to posts.