based on Postfix Setup and Dovecot setup guides, but more straightforward and with NetBSD specifics

Installation

Although we are going to use Dovecot's implementation of SASL, we could not get around this library,

pkg_add cyrus-sasl

Also we are going to enable SPF,

pkg_add py34-policyd-spf

Fetch the latest Postfix release and extract it,

wget http://mirror.netinch.com/pub/postfix/postfix-release/official/postfix-3.3.0.tar.gz
tar xzf postfix-3.3.0.tar.gz

Let's compile with TLS/openssl and SASL/dovecot,

cd postfix-3.3.0/
#make tidy
make makefiles CCARGS="-DUSE_TLS -DUSE_SASL_AUTH -DDEF_SERVER_SASL_TYPE=\"dovecot\"" AUXLIBS="-lssl -lcrypto"

make makefiles \
'CCARGS=-I/usr/local/include -I/usr/pkg/include/sasl \
-DUSE_SASL_AUTH -DUSE_CYRUS_SASL \
-DDEF_SERVER_SASL_TYPE=\"dovecot\" \
-DUSE_TLS' \
'AUXLIBS=-L/usr/local/lib -L/usr/pkg/lib/sasl2 \
-lssl -lcrypto -lsasl2'

dmesg | grep ^cpu
time make -j4

grep postfix /etc/passwd
grep postfix /etc/group
grep maildrop /etc/group

/etc/rc.d/postfix stop                               
echo 'postfix=no' >> /etc/rc.conf

make install
#ENTER ENTER... overriding the system defaults

check that make install indeed overwrote the default Postfix,

which postfix
which postconf
postconf -d|grep ^mail_ver

check that you got cyrus and dovecot enabled,

postconf -a

Configuration

cd /etc/postfix/

sed '/^#/d; /^$/d' main.cf.proto > main.cf
vi main.cf

fix some useless upstrea, defaults,

sample_directory = no
readme_directory = no

enable basic checks,

smtpd_helo_required     = yes
strict_rfc821_envelopes = yes
disable_vrfy_command = yes
smtpd_delay_reject = yes

enable NETWORK restrictions,

#postmap /etc/postfix/client_access
smtpd_client_restrictions = permit_mynetworks,
        check_policy_service unix:private/policy,
        reject_unknown_reverse_client_hostname,
        reject_unknown_client_hostname,
        check_client_access hash:/etc/postfix/client_access,
        reject_rbl_client bl.spamcop.net,
        reject_rbl_client cbl.abuseat.org,
        reject_rbl_client b.barracudacentral.org,
        reject_rbl_client zen.spamhaus.org,
        reject_unauth_pipelining

# this is too restrictive
#       reject_rhsbl_sender dsn.rfc-clueless.org,

# apews is blocking bsd.nethence.com (online.net)
#       reject_rbl_client l2.apews.org,

# sarbl.org not found
#       reject_rbl_client public.sarbl.org,

#deprecated: policy_time_limit = 3600
smtpd_policy_service_request_limit = 1
#http://www.postfix.org/SMTPD_POLICY_README.html

#reject_unknown_client_hostname --> unknown_client_reject_code
#reject_unknown_reverse_client_hostname --> unknown_client_reject_code
unknown_client_reject_code   = 554

#smtpd_client_port_logging = yes

enable HELO/EHLO restrictions,

smtpd_helo_restrictions = permit_mynetworks,
        reject_invalid_helo_hostname,
        reject_non_fqdn_helo_hostname,
        reject_unknown_helo_hostname,
        regexp:/etc/postfix/helo.regexp

#reject_unknown_helo_hostname --> unknown_hostname_reject_code
unknown_hostname_reject_code = 554

enable MAIL FROM restrictions,

#postmap /etc/postfix/sender_access
smtpd_sender_restrictions = permit_mynetworks,
        check_sender_access hash:/etc/postfix/sender_access,
        reject_non_fqdn_sender,
        reject_unknown_sender_domain

#        warn_if_reject,
#too restrictive, this prevents unreal addresses to send
#you messages.  try to book a hotel or a flight with that
#and you will feel the pain,
#       reject_unverified_sender
#unverified_sender_reject_code = 550
#unverified_sender_reject_reason = Address verification failed
#address_verify_map = proxy:btree:$data_directory/verify_cache
#address_verify_cache_cleanup_interval = 72h
#
# Postfix 2.6 and later.
# unverified_sender_defer_code = 250
#
#proxy_write_maps = $smtp_sasl_auth_cache_name $lmtp_sasl_auth_cache_name $address_verify_map $postscree
n_cache_map

enable RCPT TO restrictions

smtpd_recipient_restrictions = permit_mynetworks,
        reject_non_fqdn_recipient,
        reject_unknown_recipient_domain

#reject_unknown_sender_domain --> unknown_address_reject_code
#reject_unknown_recipient_domain --> unknown_address_reject_code
unknown_address_reject_code  = 554

enable DATA restrictions,

# Block clients that speak too early.
smtpd_data_restrictions = reject_unauth_pipelining

#smtpd_relay_restrictions (default: permit_mynetworks, permit_sasl_authenticated, defer_unauth_destinati
on)
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

smtpd_error_sleep_time = 1s
smtpd_soft_error_limit = 10
smtpd_hard_error_limit = 20

and finally configure the usual stuff,

#(default to myhostname)
myorigin = $mydomain
#(default to FQDN minus the first component)
mydomain = nethence.com
myhostname = mx.nethence.com
mydestination = $mydomain
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 PLUS DOCKER OR INTERNAL NETWORK

message_size_limit = 30720000
home_mailbox = Maildir/

#smtpd_banner (default: $myhostname ESMTP $mail_name)
smtpd_banner = $myhostname ESMTP
biff = no
append_dot_mydomain = no

queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
data_directory = /var/db/postfix
mail_owner = postfix
inet_protocols = all
unknown_local_recipient_reject_code = 550
debug_peer_level = 2
debugger_command =
         PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
         ddd $daemon_directory/$process_name $process_id & sleep 5
sendmail_path = /usr/sbin/sendmail
newaliases_path = /usr/bin/newaliases
mailq_path = /usr/bin/mailq
setgid_group = maildrop
html_directory = /usr/share/doc/html/postfix
manpage_directory = /usr/share/man
sample_directory = no
readme_directory = no
meta_directory = /etc/postfix
shlib_directory = no
compatibility_level = 2

postconf compatibility_level=2

tail -F /var/log/messages
cp /etc/localtime /var/spool/postfix/etc/localtime
ls -lhF /var/spool/postfix/etc/localtime
postfix start

Prepare some additional files as the config points to those, see above,

cat > client_access <<-EOF
compute.amazonaws.com   REJECT compute.amazonaws.com is identified as a spam domain
.compute.amazonaws.com  REJECT compute.amazonaws.com is identified as a spam domain
EOF
cat client_access
postmap client_access

cat > helo.regexp <<-EOF
/^mx\.nethence\.com$/          550 you are not me
/^nethence\.com$/               550 you are not me
EOF
cat helo.regexp 

cat > sender_access <<-EOF
securityfocus.com       OK
online.net              OK
ovh.com                 OK
EOF
cat sender_access
postmap sender_access

Now about master.cf, enable SPF checks,

sed '/^#/d; /^$/d' master.cf.proto > master.cf
vi master.cf

policy  unix  -       n       n       -       0       spawn
        user=nobody argv=/usr/pkg/bin/policyd-spf

Proceed with the rest of the usual mail setup,

vi /etc/mail/aliases

root: REAL-MAIL-USER
WHEELUSER: REAL-MAIL-USER
sales: REAL-MAIL-USER
contact: REAL-MAIL-USER

Note. abuse: postmaster is already defined by default.

Give a name to your root account (something that helps identify the system, instead of Charlie),

vipw

root@mx

Eventually make some handy symlinks,

cd ~/
ln -s ../etc/postfix
ln -s /etc/mail/aliases
ln -s /var/log/maillog

Prepare the user home dirs,

mkfs.ext2 /dev/rxbd1d
cat >> /etc/fstab <<-EOF
/dev/xbd1d              /home   ext2fs  rw               1 2
EOF
mkdir /home/
touch /home/NOT_MOUNTED
mount /home/

useradd -s /sbin/nologin -m mailuser1
ls -alhF /home/mailuser1/
passwd mailuser1
chmor 700 /home/mailuser1/

and check remotely what happens,

SMTP_ADDRESS=x.x.x.x
telnet $SMTP_ADDRESS 25

helo...
mail from...
rcpt to...

==> some Maildir/ folder gets created in the user homedir with the message inside.

Postfix // Ready to go

vi /etc/rc.local

/usr/sbin/postfix start && echo -n postfix330
...
echo '.'
/usr/sbin/postconf -d|grep ^mail_ver 

SASL

To enable SASL/dovecot,

smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
smtpd_tls_auth_only = yes
#broken_sasl_auth_clients = yes

In master.cf, disabling it on port 25 and specifically enabling it on port 587,

smtp      inet  n       -       n       -       -       smtpd
        -o smtpd_sasl_auth_enable=no

submission inet n       -       n       -       -       smtpd
        -o syslog_name=postfix/submission
        -o smtpd_tls_security_level=encrypt
        -o smtpd_sasl_auth_enable=yes
        -o smtpd_client_restrictions=permit_sasl_authenticated,reject
        -o smtpd_sender_restrictions=
        -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
        -o milter_macro_daemon_name=ORIGINATING
        -o smtpd_sasl_type=dovecot
        -o smtpd_sasl_path=private/auth

STARTTLS with Let's Encrypt

pkg_add py34-certbot
pkg_info -L py34-certbot | grep bin
mkdir -p ~/bin/
ln -s /usr/pkg/bin/certbot3.4 ~/bin/certbot

vhost=mx.nethence.com
which certbot
certbot --standalone -d $vhost certonly
#certbot --webroot -d <domain_name> -w <web_root> certonly
cp -p /usr/pkg/etc/letsencrypt/live/mx.nethence.com/fullchain.pem ~/
cp -p /usr/pkg/etc/letsencrypt/live/mx.nethence.com/privkey.pem ~/
chmod 400 ~/*.pem

Rather prepare this one on a GNU/Linux box with enough entropy (should be >3000),

cat /proc/sys/kernel/random/entropy_avail
openssl dhparam -out ~/dh.pem 4096
chmod 400 ~/dh.pem

and check remotely,

SMTP_ADDRESS=212.83.171.255
openssl s_client -starttls smtp -crlf -connect $SMTP_ADDRESS:25 </dev/null | grep ^subject

Enabling STARTTLS

smtpd_tls_cert_file = /PATH/TO/fullchain.pem
smtpd_tls_key_file = /PATH/TO/privkey.pem
smtpd_use_tls = yes
#smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
#smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtp_tls_loglevel = 1

Dovecot

wget --no-check-certificate https://www.dovecot.org/releases/2.3/dovecot-2.3.1.tar.gz
wget --no-check-certificate https://pigeonhole.dovecot.org/releases/2.3/dovecot-2.3-pigeonhole-0.5.1.tar.gz

./configure --with-dovecot=../dovecot-2.3.1

cp -Rp /usr/local/share/doc/dovecot/example-config/* /usr/local/etc/dovecot/ 
cd ~/
ln -s /usr/local/etc/dovecot
cd ~/dovecot/
mv -i dovecot.conf dovecot.conf.dist
sed '/^[[:space:]]*#/d; /^$/d' dovecot.conf.dist > dovecot.conf

grep 42 /etc/group
grep 42 /etc/passwd

groupadd -g 42 dovecot
useradd -c "Dovecot unprivileged user" -d /dev/null -u 42 -g dovecot -s /sbin/nologin dovecot

groupadd -g 43 dovenull
useradd -c "Dovecot login user" -d /dev/null -u 43 -g dovenull -s /sbin/nologin dovenull

Configure as described in the [Dovecot][dovecot] guide and check,

ls -lhF /var/spool/postfix/private/auth

Reprocessing Maildir Messages

PROBLEM: this needs to be executed as user hence enabling its shell.

pkg_add pine formail bash
ln -s ../usr/pkg/bin/bash /bin/bash

chpass -s /bin/ksh pbraun
su - pbraun

#!/bin/ksh
#
# Re-process a Maildir format mailbox with Procmail
#
set -e

[[ -z $1 ]] && print \$1 missing && exit 1
[[ ! -d $1 ]] && print folder $1 does not exist && exit 1
[[ ! -r $1 ]] && print folder $1 is not readable && exit 1

find $1/cur $1/new -type f | while read msg; do
    print Processing $msg...\\c
    procmail < $msg && rm -f $msg && print Done
done

Dovecot // Ready to go

vi /etc/rc.local

/usr/local/sbin/dovecot && echo -n dovecot

Troubleshooting

If you get this error when trying to connect tru IMAP and/or SMTP,

Error: Failed to initialize SSL server context: Couldn't parse DH parameters

==> Dovecot 2.3 requires to setup DH params: https://wiki.dovecot.org/SSL/DovecotConfiguration

References

about SASL