Recipient Verification for mandatory TLS

If mandatory TLS mode for some recipients or domains is chosen, mails may get deferred until the expiry of the maximal queue lifetime, since the "best" recipient MX server doesn't have TLS activated. This "works as designed" but may confuse your senders -- as a workaround I created recipient verification via TLS.


In present times, I often get asked about preparing mailservice with mandatory TLS policy encrypt. Think of giving this option choosable via some online GUI to your customers. Unfortunately, normal mail users are suprised about the fact, that mails which can't get delivered via TLS secure channel will not get delivered at all - instead they'll stay in the queue until the maximal queue liftime is exceeded (usually five days). A simple "one time test" of the recipients' mailserver TLS capabilities may integrated in the GUI. But this does not honor the fact, that capabilities might change any time in the future.

Solution with Postfix

My idea to workaround these problem is using the postfix recipient verify service via TLS only. During my test this works fine. But be aware, as any verify service via SMTP, there a common limitations, like best MX recipient mailserver is not reachable by whatever reason, or it use some hard way of greylisting which might result in temporary 4XX Errors to the verify service, so verify status gets undeliverable at any testing time. The postfix verify service stores the verification results in cache, you might consider to lower the default caching time, but be warned, too much and fast probing may also result in some blacklisting by the recipient mailservice.

From technical side automatic fallback to opportunistic TLS policy may can be done, i.e via smtp_fallback_relay, but this falls back no TLS modus auto.

So my opinion is, if someone decides to use TLS Level encrypt only, this should be strictly followed anyway, anycase, anytime. The best compromise seems using the verify service with via TLS only, with small modifications, have fast verify decisions with relative short caching result times, at last present a good and informal smtpd_reject_footer in case verify via TLS fails. After this ,the sender may decide switch back to probably opportunistic TLS level, which should configured as default on modern mailsystems.

You need to configure a second postfix instance. in instance in     inet  n       -       -       -       -       smtpd     inet  n       -       -       -       -       smtpd
    -o smtpd_recipient_restrictions=check_recipient_access,hash:/etc/postfix-2/tls_recipient_access,reject
verify_tls  unix  -       -       n       -       -       smtp
    -o smtp_tls_security_level=encrypt

Optional /etc/postfix-2/tls_recipient_access perhaps used as some manual whitelist, which also may created by a TLS CRON verify script. Here is a rough draft of such a script:

rm /etc/postfix-2/
for i in `cat /etc/postfix-2/tls_policy | awk 'BEGIN { FS = " " } ; { print $1}'` ; do
SERVER=`dig -t mx $i | grep "MX" | grep -v ";" | awk 'BEGIN { FS = " " } ; { print $1 " " $6 " " $5}' | sort -n -k 3 | head -1 | cut -d' ' -f2  | sed 's/.$//'`
TLSTEST=`swaks -t -tls -s $SERVER -f postmaster@$HOSTNAME -t @$i -q TLS --ehlo $HOSTNAME | grep "TLS started" | awk 'BEGIN { FS = " " } ; { print $2 " " $3}'`
if [ "$TLSTEST" == "TLS started" ]; then
echo "$i OK" >> /etc/postfix-2/
postmap hash:/etc/postfix-2/ && mv /etc/postfix-2/     /etc/postfix-2/tls_recipient_access.db
postmulti -i postfix-2 -p reload

Additional perhaps some pimp to get give more info in case verification failed:

echo "$i REJECT rejected mail to $i cause secure TLS smtp verify at $SERVER failed at $DATE" >> /etc/postfix-2/


My small tests resulted in running that script works also with Greylisting servers, but that may not guaranteed at every recipient mailserver ! Script resulting verify restriction table only, may also used in your simple none two instance setup postfix mailserver setup at smtp income level. To make sure you will get recent MX Records refresh your DNS cache before running script. in second instance:

address_verify_default_transport = verify_tls
smtp_tls_policy_maps = hash:/etc/postfix-2/tls_policy
address_verify_map = btree:/var/lib/postfix-2/verify
smtpd_recipient_restrictions = reject_unverified_recipient
smtpd_reject_footer = \c.the recipient address couldnt verified via secure tls channel
unverified_recipient_reject_code = 550
address_verify_poll_count = 3  # default
address_verify_poll_delay = 3s # default
address_verify_sender = <> # try postmaster
address_verify_positive_expire_time = 1d
address_verify_positive_refresh_time = 1d
address_verify_negative_refresh_time = 1d
address_verify_negative_expire_time = 1d
address_verify_negative_cache = yes

Finally limit the bounce time global in second instance as final solution if any verify results are buggy by whatever reasons and mail was queued.

bounce_queue_lifetime = 12h
delay_warning_time = 6h

/etc/postfix-2/tls_policy encrypt in first instance

transport_maps = hash:/etc/postfix/transport

/etc/postfix/transport  verify_tls_fallback:[]:10025
# verify_tls_fallback:[]:10025

You like to i.e use sql tables, but using them for transport is not recommended, so an alternative might be using FILTER first instance

verify_tls_fallback  unix  -       -       n       -       -       smtp
    -o smtp_fallback_relay=


The Swiss Army Knife SMTP Tool swaks may give more opportunities to feed its results in a i.e a Database. Swaks may also validate and display ssl crts.

swaks -t -tls -s -f -t -q TLS --ehlo --tls-verify --tls-ca-path /etc/ssl/certs --tls-get-peer-cert

This will produce some output like this

=== Trying
=== Connected to
<-  220 ESMTP Postfix
<-  250-SIZE 40960000
<-  250-ETRN
<-  250-STARTTLS
<-  250-8BITMIME
<-  250 DSN
<-  220 2.0.0 Ready to start TLS
=== TLS started with cipher TLSv1:ECDHE-RSA-AES256-SHA:256
=== TLS no local certificate set
=== TLS peer DN="/serialNumber=tumaqyzmnDu8m1ss6qhFTdtgazyg6/An/OU=GT53160044/OU=See (c)12/OU=Domain Control Validated - RapidSSL(R)/"
=== -----END CERTIFICATE-----
<~  221 2.0.0 Bye
=== Connection closed with remote host.

if validate will fail you will see something like this:

*** TLS startup failed (connect(): error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed)
*** STARTTLS attempted but failed

Please be aware that it may always fail with self created ssl crts !

If you like, grep your existing logs from a mailserver with opportunistic TLS for hostnames SMTP TLS was working in the past, check them again with swaks, store ssl crt , cipher or whatever you like in a database and compare results from time to time with some alarming if something has changed i.e for human security inspection. Or you might consider doing automatic change to mandatory TLS with domains/hosts where TLS SMTP worked in the past with opportunistic TLS but get alarmed if some ssl parameters changed on recipient mailservers.


This Solution is not the Holy Grail, but worked in testing environment, it might be better to have some milter or policy server to handle tls verification, which should include cache and is tolerant of any case of tmp errors or connection problems on the recipient side. Some other idea maybe is using an access table feeded from some daily cron TLS check verify script to include in second instance ( see example ), or feed CRON verify TLS check results in your own RBL. Perhaps include that RBL in second instance. Some main advantage of using your own RBL might be, it should be includable in multiple postfix servers more easy. Anyway, you might play around with other cascading order of instances, or do other combining of restrictions, perhaps you might think using a verify script access table only solution is better design. Finally Wietse Venema has released a patch with postfix-2.12-20140316. This resolves some old requests to make hard/soft errors more configurable, this may also help in early bounce TLS mandatory Modus.

Robert Schetterer, 02 Mar 2014

   postfix    tls    verify