How to configure postfix in 2024.
References
- https://prefetch.eu/blog/2020/email-server/
- https://www.linux.org/threads/local-postfix-cannot-send-to-gmail.49489/
- https://apedik.dev/blog/post/rewrite-postfix-email-sender-with-postsrsd.html
- https://wiki.archlinux.org/title/Postfix_with_SASL
- https://wiki.archlinux.org/title/Postfix
- https://www.mail-tester.com/
1. Preface
At some point I had to move from OpenBSD's OpenSMTPd to Slackware's Postfix.
It turned out to be surprisingly hard to configure, so I decided to keep a log of things I have done in order to remember them, and in order to help future mail server administrators.
In a few words, I am not amused: Postfix is over-engineered and pretends to be modular and modifiable, like a "true UNIX software", but in reality it is not, it is just that its features are scattered among components, and are hard to navigate around.
Still, there is a lot of culture around it, and it also allows one to do quite a lot of things, which walled gardens, such as Telegram, WhatsApp, Wechat, and similar ones do not provide.
I have been asked to mention some "easy to setup" alternatives to Postfix in this howto (mailinabox, docker-mailserver, mailu, mailcow).
I am mentioning them there, as requested, with a personal recommendation to NEVER EVER USE THEM IF YOUR SANITY IS DEAR TO YOU.
They are solving exactly the kind of problems which are best solved by NEVER ALLOWING THEM TO EXIST IN THE FIRST PLACE.
No only does the person deploying them have to learn 16 (not joking) programs instead of 1, that person also has to learn them in once batch, all at the same time, otherwise debugging lost mail, mail loops, and wrong deliveries becomes nearly impossible, and to complicate things even further, they make the user jump through all of the insane network forwarding and data-persistence hoops that docker
incurs.
2. Body
2.1. What is this howto aimed at?
This howto is aimed at someone installing Postfix at his or her own VPS, in order to have a self-hosted mail server for one or several people (not a company), and enjoy the benefits that self-hosted mail provides.
The howto tries to follow two principles:
- Make things as simple and minimalist as possible, but allow further scaling when required.
- Provide as many test cases as possible.
With an exception of hosting a somewhat dated submission
STARTTLS service on port 587 (which can be omitted), and the log watcher (which is still immensely helpful), none of the steps in this HOWTO can be avoided.
Rewriting addresses in case of mail forwarding are done using an improvised method, which might be less beneficial than postsrsd
.
The author is interested in feedback on this subject: if some reader includes postsrsd
and finds that its rewriting method is better in some respect, the author would be interested in comments.
2.2. What are pre-requisites for using this howto?
Setting up your own mail is not that simple, and this is unavoidable, because E-Mail is a social system. Yes, it is a social system, because it can be used by people talking to people. And as social systems evolve, they start to exhibit properties of social systems observed already by Plato and Aristotle.
E-Mail used to be a very democratic system, and although gradually over time it became more oligarchic, to combat some ochlocratic tendencies, such as SPAM. Even so, the presence of multiple oligarchs which are competing among themselves, so far has left us able to flow between the raindrops.
Matching oligarchs requirements requires a bit of work, but so far most of this work is possible to perform cheaply, or even with no money spent at all.
- Domain name. :: I don't know a way to get one for free, but … sometimes you can get one from your ISP.
- DNS provider. :: Well, you can host one yourself, but there are free providers over there, with an excellent interface.
- An unassuming and unpretending free email provider which can receive mail. :: Same thing. This is not strictly requires, but nice to have. Let's call him badmail.test
- A hosting provider with a VM or at least with a mutable container. :: You can host at home, of course, but a "Cloud VM" is better.
- A real email provider that you are using when your self-hosted mail is down. Say, oligarch.com
- A way to pay for all of those. :: Paypal? Bitcoin? Card? Whatever.
2.3. I have messed up completely a lot of things, how can I drop everything?
postqueue -j | jq -r '.queue_id' | postsuper -d -
or
postqueue -d ALL
you can also stop all the services mentioned in this howto by running
/etc/rc.d/rc.postfix stop ; /etc/rc.d/rc.opendkim stop
2.4. What is the full list of software used in this HOWTO?
- postfix
- opendkim
- msmtp
- acme.sh
- nail
Occasionally mentioned are:
- postsrsd
- opensmtpd
- ssh
- tmux
- tcpdump
- tshark/wireshark
You may have to search for what some of those programs do.
2.5. How do I change something and see what it does?
I did most of the debugging having a tmux
session on the remote server, with 3 windows open:
- Console for sending mail with
nail
ormsmtp
tail -f /var/log/maillog
tcpdump -vvv -n -i any 'port 25'
I know what you are thinking about "tcpdump? wtf?", but yes, tcpdump
.
SMTP is a plain-text protocol, so you can see where bad things happen.
You have to turn tls off though.
2.6. What is tls and why would I need it?
TLS is the server-side encryption standard we use today. It essentially consists of two parts: private key and public key. Public key is used by everyone to encrypt messages sent to you, and private key is used by you to decrypt those messages.
Of course, TLS does not just work in one direction, it works in both directions, but to initiate a connection it is enough to send a private message to recipient once.
I suggest getting a tls certificate using acme.sh
with a DNS challenge.
You can use HTTP challenge too, but it requires maintaining an HTTP server, and is generally less fun to setup as it is more stateful.
2.7. How do I obtain certificate for encrypting my mail?
Use acme.sh
I will not write the full script here, but you need to run the script twice, to apply for a cert and to issue a cert.
acme.sh --home /root/.acme.sh --server letsencrypt --issue --days 60 --dns -d domain.test -d subdomain1.domain.test -d subdomain2.domain.test --yes-I-know-dns-manual-mode-enough-go-ahead-please
for i in "${domainArray[@]}" ; do update_ddns.sh ; done
acme.sh --home /root/.acme.sh --server letsencrypt --issue --days 60 --renew --dns -d domain.test -d subdomain1.domain.test -d subdomain2.domain.test --yes-I-know-dns-manual-mode-enough-go-ahead-please
This is finicky funky stuff, but you should get it eventually.
It will produce two files: tls-certificate-acme.sh.fullchain.crt
, the public key, and tls-certificate-acme.sh.key
, the private key.
2.8. How do I enable mail on a machine?
chmod +x /etc/rc.d/rc.postfix
/etc/rc.d/rc.postfix start
But it is better to first enable both Internets we are living in now in postfix
's main.cf
+#inet_protocols = ipv4 +inet_protocols = all
2.9. And that is it? Mail would work now?
Let us check.
2.9.1. Test 1: normal mail within a machine.
As root
echo test | nail -s "Test $(( I = I + 1 ))" user
This should create a message in the user's "mailbox", which is usually /var/spool/mail/user
2.9.2. Wait, what the hell? Are you suggesting that I send mail "from a user to a user on the same machine"? This sounds like pinging localhost.
Well, not really. It is, indeed, not terribly useful by itself, but email is a thing which is inadvertently implementing a very widespread data structure: a series of entries with headers.
This data structure is so ubiquitous that we often do not even recognise it, but it everywhere.
It allows a lot of operations necessary for meaningful data organisation of a human.
In theory you could put everything into a single file, which is trivial to implement, because:
- a flat file does not have boundaries between messages
- is append(overwrite)-only
- filtering is by pattern only
A messagebox is strictly stronger than a flat file:
- you can delete messages
- you can sort the box
- you can filter messages by context
- (optional) messages might be linked into a tree
A messagebox is weaker than a directory, because a messagebox does not support tree-like organisation. However, the maildir format, combined with IMAP allows a tree-like directory for messages. In theory this makes mail as expressive as a file system.
There is the GMail system of mail organisation using labels. Labels are a totally different beast, and I do not yet have a clear opinion on them. They are also ubiquitous, but I need to think more about them.
But "mail" is readily available, and is "at least" the most standard way to report actionable errors to the user on UNIX. If something breaks, is one-off, deserves user's attention, the best bet is to send an error message to the user in question.
Sometimes it is okay to just append an error message to the log file, but, again, the point is that messages are mutable, so a user can clear them off, whereas log files are append-only and are dedicated to faithfully representing history.
2.9.3. Why would I even send those errors using "email" to a different machine? Why not just copy it using "scp
?
You can, and possibly even should, if (a) you trust your server enough to put an ssh login key onto this server, (b) your target machine accepts ssh/scp.
This is often not the case, and badmail.test
does not accept ssh
at all.
Moreover, email is, so far, the only method of receiving messages anonymously, and which has a lot of provisions for reducing even anonymous spam.
2.9.4. Okay, can I send those "errors" to a user on a different machine?
Well, yes. You can do something like this:
MX=$(dig MX badmail.test | grep -A1 'ANSWER SECTION' | tail -n 1 | awk '{print $6;}') MX="${MX%.}" echo test | nail -s "Test $(( I = I + 1 ))" \ -S mta="$MX" \ user@badmail.test
This is likely going to fail even on badmail.test
.
Firstly, because even badmail.test
is going to reject your mail with a thing called "Greylisting".
That is they will give you a "soft error", like "we are busy, come later".
To avoid this loop, and to avoid querying the mail server yourself, you are expected to use this postfix.
2.9.5. Test 2: sending mail using postfix.
MX=$(dig MX badmail.test | grep -A1 'ANSWER SECTION' | tail -n 1 | awk '{print $6;}') MX="${MX%.}" echo test | nail -s "Test $(( I = I + 1 ))" \ user@badmail.test
This will also probably fail, and you won't even know this, because postfix will not even send a "bounce" (delivery error) message to you, so you have to look for a "bounce" in a log file.
The message will probably be that the sender address (that is effectively user@mymachine.local
) is not valid.
It is not valid because mymachine.local
is not a globally recognised domain name.
Now did I mention registering a name on DNS in order to send mail from your own name? Maybe I didn't, but you have to, obviously.
But even giving this machine a real name will not solve your issue, because those domain names might expire, and you want your errors to be delivered with certainty.
But, yes it is the time to find out how to register a domain, and be ready to give it to your machine.
Well, let us first identify how to properly send mail "by hand".
2.9.6. Test 3: sending mail using an "out-of-band method".
MX=$(dig MX badmail.test | grep -A1 'ANSWER SECTION' | tail -n 1 | awk '{print $6;}') MX="${MX%.}" l_pattern='bounced' nohup tail -n0 --follow=name /var/log/maillog 2>/dev/null | \ grep --line-buffered -i -F "$l_pattern" | \ tee | \ while read -r l_inputline ; do while ! su nobody -s/bin/sh -c \ "nail -s '/var/log/maillog:bounced' \ -r 'account+mailer@badmail.test' \ -S mta='smtp://${MX}:587 \ -S smtp-use-starttls \ -S smtp-auth=none \ -S v15-compat=yes \ account+mailer@badmail.test" <<< "$l_inputline" do rc=$? logger "sending bounce notification failed:inputline=$l_inputline" sleep 60 done & disown done
I added a little more here to help you. This is effectively a log watcher, which can watch for all sorts of errors, and mail them to your receive-error-only mail account. This one watches for mail bounces, but it could watch for anything.
The positive side of this watcher is that it does not require the mail server to work on its own.
Now what is important in this log watcher script?
Do you see the -r 'account+mailer@badmail.test'
line there?
It could have been -r ''
, which is called "null recipient".
(Actually maybe this is even better.)
It lets you go around that error of the original server having a broken hostname. Again, we want to receive error messages even if our server is totally mis-configured (say, by invaders).
2.9.7. How do we introduce this wonderful sender rewriting in postfix
?
Okay, I already mentioned the config file called main.cf
, where most of the postfix
configuration go, now I will mention main.cf
, which is its "service supervisor" config file.
master.cf
defines the components that postfix
is running.
We will later use it to allow message submission on encrypted ports, but now we will define a separate delivery method (using msmtp) for our badmail.test
.
Add the following line to your master.cf
:
msmtp_for_badmail unix - n n - 50 pipe flags=R user=nobody argv=/etc/bin/postfix-to-msmtp-wrapper.bash --sender=${sender} --user=${user} --extension=${extension} --recipient=${recipient}
Where /etc/bin/postfix-to-msmtp-wrapper.bash
is a script which reads the arguments and calls msmtp
about like this:
exec msmtp --host="$MX" --port=587 --tls=on --from="$recipient" "$recipient" 2>&1
I won't give you full script, this is a trivial exercise.
You also need to specify that mail to this server should be sent in a special way.
main.cf
minimal_backoff_time = 60s maximal_backoff_time = 600s transport_maps = regexp:/etc/postfix/transport_maps
And in transport_maps
:
/^.*@badmail.test/ msmtp_for_badmail:
2.9.8. Test 4: Does this work now?
Well, test it by sending mail like this:
echo "Test $(( I = I + 1 ))" | nail -s "Test $I mail to badmail" \ root@my-domain.test
This should pass.
Look into your /var/log/maillog
for greylisting errors and such, and wait a few minutes for the message to be delivered.
2.10. How do I get the wonderful error messages delivered to me? The system won't know my badmail
address?
Good.
So, there are two methods:
- aliases
- bcc
Aliases is the file /etc/aliases
, which allows you to show where to duplicate a message if you are sending it to a local user.
You can check its syntax by man aliases
, and do not forget to run postalias
after editing it.
However, for a start, I recommend editing always_bcc
in main.cf
.
Set it to your badmail
address.
#main.cf always_bcc = user@badmail.test
In addition, I suggest adding the following setting in order to receive error reports from postfix
itself when things go wrong:
notify_classes = bounce, delay, policy, protocol, resource, software
Errors are not subject to always_bcc
, so you will need to edit /etc/aliases
anyway (that is why introduced it first) and add user@badmail.test
after postmaster
.
postmaster
is a magical domain which is there for receiving mail errors.
cd /etc/postfix ; postalias aliases
2.10.1. Test 1: local delivery redirected to badmail
.
This time I suggest not writing a mail, but rather making a cron
entry like this:
* * * * * echo "Test $(date --iso=seconds)" 1>&2
Messages should be delivered to both user inbox, and your wonderful badmail
account.
2.10.2. Test 2: remote delivery over port 25.
In general you want to have your mail encrypted. However, receiving mail over the classical unencrypted port 25 would still be nice if you have some old devices, and old does not mean that old, at the time of writing Android 6 already have expired TLS certificates and balk at random occasions.
By default Postfix allows unencrypted port 25 mail for local delivery, so let us try it from your laptop.
echo "Test $(( I = I + 1 ))" | nail -v -s 'test-public-port-25' \ -S mta='smtp://subdomain.my-domain.test:25' \ -S smtp-auth=none \ -S v15-compat=yes \ user@my-domain.test
This should work and deliver mail to user
, as well as duplicate it to your badmail
.
2.11. How do I enable encryption in postfix
and support outbound encryption?
#main.cf smtp_tls_security_level = may smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt smtp_tls_loglevel = 1
2.11.1. Test 1: sending outbound TLS-encrypted mail.
echo "Test $(( I = I + 1 ))" | nail -v -s 'test-public-port-25-encryption-outbound' \ -S v15-compat=yes \ noreply@oligarch.com
And look at the postfix
log.
Delivery should fail, but Postfix log should have information about the TLS quality.
You can disable smtp_tls_loglevel
afterwards.
2.12. How do I add encryption to a default port 25?
Again, it is better to only add optional encryption, since receiving unencrypted mail can be useful.
We will use those keys that you should have prepared 2.2
# main.cf smtpd_tls_security_level = may smtpd_tls_cert_file = /etc/ssl/tls-certificate-acme.sh.fullchain.crt smtpd_tls_key_file = /etc/ssl/private/tls-certificate-acme.sh.key
Test from your laptop by:
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-public-port-25' \ -S mta='smtp://mailer.domain.test' \ -S smtp-use-starttls\ -S smtp-auth=none\ -S v15-compat=yes\ user@mailer.domain.test
This should succeed, because mail should be accepted to a local destination.
Also test on your laptop:
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-public-port-25' \ -S mta='smtp://mailer.domain.test' \ -S smtp-use-starttls \ -S smtp-auth=none \ -S v15-compat=yes \ user@oligarch.com
This should fail, because relaying to oligarch.com over port 25 should be refused even the port supports encryption.
2.13. Is this the time to set up domain name, DNS, and all that magic?
Yes. So far we have done everything that can be done without exposing ourselves to the world as people with an identity. We used IP addresses, nonexistent domain names, and sender rewriting. Now we are planning to interact with the external world.
Some of it can be done with self-generated keys and localhosted domains, but really the next steps are about interacting with the rest of the world.
Let us assume that your domain is domain.test
.
- Generate a DKIM key buy running
/etc/rc.d/rc.opendkim
(and add starting it torc.local
) - You need at least four
A
and (AAAA
) records, fordomain.test
and formailer.domain.test
, with IP addresses. - You will need least one
MX
record withmailer.domain.test
. - You will need an SPF (TXT) record like this:
v=spf1 mx -all
ondomain.test
, and maybe"v=spf1 a -all"
onmailer.domain.test
. - You will need a DMARC record
_dmarc.domain.test
like this"v=DMARC1;p=none;pct=100;rua=mailto:postmaster@mailer.domain.test
, this is for the place where spam violations will be sent. (Aren't those the errors which we have done so much to be able to receive?) - Set up a DKIM record
default._domainkey.domain.test
with the value thatopendkim
has generated for your in/etc/opendkim/keys/
. These are NOT the TLS keys.
2.14. Shall I set up DKIM right now?
Well… technically you can, and this would work, but would not be terribly useful, since the mail server would have the SPF trust level anyway.
So, surprisingly, I refer you to the point to really read about DKIM and OpenDKIM.
2.15. How do I add authenticated encryption to submit email?
Add the following to main.cf
submission inet n - n - - smtpd -o syslog_name=postfix/submission -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes -o smtpd_tls_auth_only=yes -o smtpd_reject_unlisted_recipient=no # -o smtpd_client_restrictions=$mua_client_restrictions # -o smtpd_helo_restrictions=$mua_helo_restrictions # -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_recipient_restrictions= -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING smtps inet n - n - - smtpd -o syslog_name=postfix/smtps -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes -o smtpd_reject_unlisted_recipient=no # -o smtpd_client_restrictions=$mua_client_restrictions # -o smtpd_helo_restrictions=$mua_helo_restrictions # -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_recipient_restrictions= -o smtpd_relay_restrictions=permit_sasl_authenticated,reject -o milter_macro_daemon_name=ORIGINATING
Sometimes your system might have submissions
instead of smtps
.
Mapping of "services" to ports is done in /etc/services
.
The important bit here is smtpd_relay_restrictions=permit_sasl_authenticated
.
It will prohibit sending mail with this port, unless one is authenticated using "sasl". SASL is essentially "login/password smtp authentication". It is not the only model which makes sense even for a home setup. For example, one might imagine a TLS client-certificate authentication.
You need to run the following tests:
2.15.1. Test 1 starttls on port 587
On your laptop:
echo "Test $(( I = I + 1 ))" | nail -s 'test-submission-port-587' \ -v\ -S mta='smtp://mailer.domain.test:587' \ -S smtp-use-starttls \ -S smtp-auth=none \ -S v15-compat=yes \ root@mailer.domain.test
Connection should work, but sending mail should fail with the reason:
SMTP server: 554 5.7.1 <root@mailer.domain.test>: Recipient address rejected: Access denied
I haven't investigated it deeper, but it seems that this message is misleading. It is not the recipient address which is rejected, it is that the client is not authorised. The reason this happens is that postfix processes the client connection line by line, and the authentication is checked after the RCPT TO command is issued by the client.
SMTP does not have anything like "AUTH anon" command, which could fail with something like "unauthorized", the command may simply be missing, therefore the place where postfix fails is RCPT TO.
Why the error message is misleading? Well, I don't know.
2.15.2. Test 2 starttls on port 465
On your laptop.
echo "Test $(( I = I + 1 ))" | nail -s 'test-submission-port-465' \ -v\ -S mta='smtp://mailer.domain.test:465' \ -S smtp-use-starttls \ -S smtp-auth=none \ -S v15-compat=yes \ root@mailer.domain.test
This test should connect, but timeout, because STARTTLS is a plain TCP connection, whereas port 465 expects a TCP+TLS conection.
2.15.3. Test 3 tls on port 465
Note that there is no smtp-use-starttls
, but there is smtps://
.
On your laptop.
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-submission-port-465-tls' \ -S mta='smtps://mailer.domain.test:465'\ -S smtp-auth=none \ -S v15-compat=yes \ root@mailer.domain.test
This test should fail with the message:
SMTP server: 554 5.7.1 <root@mailer.domain.test>: Recipient address rejected: Access denied
.
Again, this message is misleading, see Test 1.
2.16. How do I make myself authenticated on the two submission ports?
So, in OpenSMTPd this is super easy, just use the listen
directive with an auth
keyword, and users will be authenticated with their system login/password pair.
But Postfix is enterprise-level software, so it expected something called SASL-daemon.
Slackware has cyrus-sasl
and rc.saslauthd
.
I used the following guide, https://wiki.archlinux.org/title/Postfix_with_SASL , but the pam_other
issue that they are mentioning does not exist on Slackware, so things are a bit easier.
The official documentation of Postfix surprisingly makes sense in this question too (http://www.postfix.org/SASL_README.html).
- enable
rc.saslauthd
:: chmod +x /etc/rc.d/rc.saslauthd - Edit /etc/sasl2/smtpd.conf
- /etc/rc.d/rc.saslauthd restart
- Nothing required on the postfix side, because
permit_sasl_authenticated,reject
has already been introduced, but you can restart postfix for clarity./etc/rc.d/rc.postfix restart
# /etc/sasl2/smtpd.conf pwcheck_method: saslauthd mech_list: PLAIN LOGIN log_level: 7
So, SASL is a generic authentication daemon (which includes a plain password method).
PAM is a system login method.
Postfix asks SASL, and gives it a login and a password, and SASL asks PAM, and forwards the login and password to it.
PAM can delegate password checking again somewhere, say, to SASL, which will in turn ask PAM, and so on and so on, and we will have a nice sweet authentication loop which will hang your system.
But usually PAM asks the passwd
(UNIX) database instead.
Important note: your password must be as simple as possible, in order to not be mangled by byte-encoding conversions, and other annoying system limitations. It does not, however, mean that your password should be weak, you should make it strong by the most obvious method – make it looooong. At least 64 symbols, desirably all from just Latin alphabet, possibly with digits.
In fact, you can make SASL
check the shadow
database by itself, without PAM, or it can even query its own database sasldb
.
You can also make Postfix
query Dovecot, not using cyrus-sasl
at all.
2.16.1. Test 1 that sending authenticated mail works.
On your laptop:
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-submission-port-465' \ -S mta='smtps://login:password@mailer.domain.test:465'\ -S v15-compat=yes\ root@mailer.domain.test
This should pass, and your root should receive this message, as well as your debugging account on an unrelated mail service.
2.16.2. Test 2 that wrong password fails.
On your laptop:
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-submission-port-465' \ -S mta='smtps://login:wrong_password@mailer.domain.test:465'\ -S v15-compat=yes\ root@mailer.domain.test
This test should, obviously, fail.
The error should be 535 5.7.8 Error: authentication failed: authentication failure
.
2.17. Can I send normal mail now?
Well, let us check:
2.17.1. Test 1: from mailer to some machine.
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-submission-port-465' \ -S mta='smtps://login:pass@mailer.domain.test:465'\ -S v15-compat=yes\ -r 'user@mailer.domain.test' \ my-nick@oligarch.com
This may or may not work without DKIM. In general, SPF alone should be enough, if it is not, go to But this is not very sweet, because we do not want to send mail from the mailer machine, unless it is diagnostic mail. What we really want is "cloud mail", that is sending mail from an address like "my-nick@domain.test", not "user@mailer.domain.test".
Let us go to the test number 2.
2.17.2. Test 2: from a public address to a public address.
On your laptop:
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-submission-port-465' \ -S mta='smtps://login:pass@mailer.my-domain.test:465'\ -S v15-compat=yes\ -r 'user@my-domain.test' \ my-nick@oligarch.com
This may or may not work without DKIM. Again, SPF is often enough.
2.18. How can I receive mail?
By this time you should be able to receive mail to "user@mailer.domain.test", but receiving mail to a mailer user is not terribly useful. Sometimes you may want to make your systems exchange logs or certificates, but even in this case, doing those exchanges by mail is harder than over, say, SSH. The only benefit really is being able to accept anonymous messages from machines which do not have proper ssh, or have outdated TLS certificates, or such.
Now when you receive mail, you have a painful choice to make:
- Do you want to maintain your own JMAP/IMAP server?
- Do you want to just forward everything to an oligarch and call it a day?
In theory the option 1 is better, because it actually allows you to control your information flow as you want it. However, the option 2 is not without its merits: (1) many people do not have a good information flow system, (2) those who do may already have an established workflow with an oligarch's service, (3) in some cases an oligarch's service ends up less costly.
My choice is going with an oligarch, which creates one more choice:
- Make the oligarch's service collect your mail.
- Make your server send the mail to the oligarch.
Option 1 is usually better, because it is more likely to catch errors – if you configure oligarch.com to collect your mail by POP3, OLigomail will complain if your server ends up being broken. However, this requires maintaining your own POP server, requires more "state" to be configured on it, and not all oligarch's services support mail collection.
So let us do option 2.
2.18.2. Add our domain to virtual_maps
Virtual maps is effectively what allows forwarding of mail that does not belong to the world of UNIX machines talking to each other, and instead connects people among themselves.
Postfix has two different kinds of "virtual" redirections, "virtual aliases" and "virtual mailboxes". The documentation for them is obscure and impenetrable that it's unlikely that a human being can understand it, but if you wish to try, here is the link:
#main.cf virtual_alias_domains = my-domain.test virtual_alias_maps = regexp:/etc/postfix/virtual_maps_to_oligarch
# /etc/postfix/virtual_maps_to_oligarch /^(.*)@my-domain.test/ user@oligarch.com
cd /etc/postfix ; postmap virtual_maps_to_oligarch
- Test 1
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-send-to-my-domain'\ -S mta='smtps://mailer.my-domain.test \ -S smtp-use-startls \ -S smtp-auth=none \ -S v15-compat=yes \ -r 'someone@my-domain.test' \ someone@my-domain.test
This should pass, because our
MAIL FROM
, or reverse address, is frommy-domain.test
, but this test will probably fail: - Test 2
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-send-to-my-domain'\ -S mta='smtps://mailer.my-domain.test \ -S smtp-use-startls \ -S smtp-auth=none \ -S v15-compat=yes \ -r 'someone@not-my-domain.test' \ someone@my-domain.test
2.18.3. Enable OpenDKIM to sign all your mail.
We never intend to validate dkim, but we want to sign DKIM on at least the messages that we ourselves send from 'user@my-domain.test'
.
Well, actually, the actual thing is that we will sign all mail regardless.
We do not intend to do any relaying of alien mail, all the mail is either originating from us (and thus deserves a signature), or is coming to us, so having it signed does not hurt.
OpenDKIM is very annoying to set up.
Set up signing with OpenDKIM
#main.cf smtpd_milters = inet:localhost:8891 non_smtpd_milters = inet:localhost:8891
LogWhy yes Syslog yes SyslogSuccess yes Canonicalization relaxed/simple Mode s ExternalIgnoreList /etc/opendkim/refile_match_everything InternalHosts /etc/opendkim/refile_match_everything KeyTable refile:/etc/opendkim/keytable SigningTable refile:/etc/opendkim/signingtable Socket inet:8891@localhost ReportAddress postmaster@my-domain.test SendReports yes UserID opendkim:opendkim
#/etc/opendkim/refile_match_everything 127.0.0.1 ::1 ::/0 0.0.0.0/0
#/etc/opendkim/keytable default._domainkey.my-domain.test mydomain.test:default:/etc/opendkim/keys/default.private
#/etc/opendkim/signingtable * default._domainkey.my-domain.test
Test by trying to send mail to your friend, or your second OLigomail account and monitoring either OLigomail "Show original", or postfix
log files for DKIM.
- Test 1
echo "Test $(( I = I + 1 ))" | \ nail -v -s 'test-send-to-my-domain'\ -S mta='smtps://mailer.my-domain.test \ -S smtp-use-startls \ -S smtp-auth=none \ -S v15-compat=yes \ -r 'someone@domain.test' \ someone@oligarch.com
Now this should work with no issues. It should have both SPF and DKIM succeeding.
2.19. Should all forwarding now work?
Lol, now. Sometimes you have addresses like this:
- MAIL FROM ::
cmake+verp-3d0a58993745ed18eea3ea75a1641ead@discourse.cmake.org
- From:
noreply@cmake.org
DMARC for cmake.org
is set up so as to prevent forwarding, so if you forward message with these addresses, and a DKIM sign, to OLigomail, it will be rejected.
What shall we do?
We will rewrite the MAIL FROM
address just like we did with mail to badmail
.
Add another transport:
#master.cf msmtp_for_olig unix - n n - 50 pipe flags=R user=nobody argv=/etc/bin/postfix-to-msmtp-wrapper_olig.bash --sender=${sender} --user=${user} --extension=${extension} --recipient=${recipient}
# /etc/bin/postfix-to-msmtp-wrapper_olig.bash l_sender="${sender/@/==}"_rewrite@my-domain.test MX=$(dig MX oligomail.com | grep -A1 'ANSWER SECTION' | tail -n 1 | awk '{print $6;}') MX="${MX%.}" exec msmtp --host="$MX" --port=25 --tls=on --from="$l_sender" "$recipient" 2>&1
In my case it worked for 90% of the cases, with an exception of SPF-enabled domains without DKIM. I will leave dealing with such domains as a simple homework to the reader.
2.19.1. Test 1: mail from some server.
Try registering at cmake discourse, and look in postfix logs if the message goes through.
2.20. How to check that my server is not slow?
https://serverfault.com/questions/24121/understanding-a-postfix-log-file-entry
For each message there will be two entries: delay
and delays=a/b/c/d
, they mean time spent by Postfix on processing the message.
- a :: receiving SMTP session
- b :: in queue
- c :: connection setup
- d :: message transmission
3. Afterword
I am not amused. Postfix' routing model is strictly weaker than that of OpenSMTPd. It's syntax is queer, and its defaults are dubious. Its documentation is pile of toilet paper that is so hard to get through that everyone who is getting through installing his own server ends up writing a HOWTO "how I did it". It is extremely verbose and at the same time inflexible.
The configuration that in OpenSMTPd required just 21 lines in a single file, ended up needing