Set up a transactional mail server
A transactional email is generated by many popular packages, typically for account registration confirmation, topic reply notifications, password retrieval, and other automated uses such as sending system notification messages to administrators. The emails will commonly be sent from an email address that is a non-functioning email account used exclusively for sending, such as NO-REPLY@example.com.
The PHP mail()
function is typically the default method that PHP packages will use for sending transactional mail. This function will use the server's sendmail-compliant MTA to send the mail, thus necessitating a sendmail-compliant MTA to be installed. An MTA can be used to send mail directly from the server or can be configured to send mail through a smart host to reduce server and admin overhead.
Most of the popular PHP packages will include an option or have a plugin available to send transactional email through an SMTP server, eliminating the requirement to install any type of MTA for the purpose of supporting email generated by the package. However, without an MTA installed, system messages cannot be sent to the administrators.
Transactional email via MTA
Log in as root and install the necessary packages:
username@servername:~$ sudo -i root@servername:~# aptitude install postfix opendkim opendkim-tools
Select 'Internet site' during the postfix
install.
Configure OpenDKIM
The opendkim
package will add a DKIM signature to configured outbound emails. Receiving servers can compare this signature with the corresponding DNS DKIM record and verify messages as coming from an approved server and unaltered.
root@servername:~# mkdir -p /etc/opendkim/keys/example.com root@servername:~# opendkim-genkey -r -b 2048 -h sha256 -d example.com -s selector -D /etc/opendkim/keys/example.com
Replace selector
as desired, as it is only used to identify the key the server will call. Many admins will simply use the date the key was created (e.g. '20140510') or, when multiple servers are being used, the server name (e.g. 'mta05'), though none of this is standardized or required - some simply use 'mail', or don't specify anything when running the command, leaving the selector of 'default'. The command creates two files, selector.private
and selector.txt
. The selector.private
file contains the private key while selector.txt
contains the basis for the DNS TXT record that will be created in a later step.
Note that the DKIM standard currently recommends a maximum key size of 2048 bits, so using a larger key size will likely cause the DKIM test to fail on many servers, while using a key smaller 1024 bits is not only not recommended, but has been demonstrated to be insecure.
KeyTable
Create the tables that opendkim
will use:
root@servername:~# nano /etc/opendkim/KeyTable
Add:
example.com %:selector:/etc/opendkim/keys/example.com/selector.private
Since this server is being configured to have one key serve mail for multiple domains, only one private key is being used. The key could be named anything and does not have to be a domain. The %
instructs opendkim
to add the domain being sent into the DKIM header d=
value.
SigningTable
root@servername:~# nano /etc/opendkim/SigningTable
Add:
*@example.com example.com *@subdomain.example.com example.com *@example.org example.com
The second and third entries demonstrate how additional domains served by the mail server would be added to the signing table.
TrustedHosts
root@servername:~# nano /etc/opendkim/TrustedHosts
127.0.0.1 ::1 localhost example.com
Configure opendkim.conf
Set permissions on the directory, archive the default opendkim.conf
before creating a new one:
root@servername:~# chown -R opendkim:opendkim /etc/opendkim root@servername:~# mv /etc/opendkim.conf /etc/original.opendkim.conf root@servername:~# nano /etc/opendkim.conf
Add to the new file:
# This is a basic configuration that can easily be adapted to suit a standard # installation. For more advanced options, see opendkim.conf(5) and/or # /usr/share/doc/opendkim/examples/opendkim.conf.sample. # Log to syslog Syslog yes # Required to use local socket with MTAs that access the socket as a non- # privileged user (e.g. Postfix) UMask 002 # Commonly-used options SubDomains yes AutoRestart yes Background yes Canonicalization relaxed/relaxed DNSTimeout 5 Mode sv SignatureAlgorithm rsa-sha256 # Additional OpenDKIM options ExternalIgnoreList refile:/etc/opendkim/TrustedHosts InternalHosts refile:/etc/opendkim/TrustedHosts KeyTable refile:/etc/opendkim/KeyTable SigningTable refile:/etc/opendkim/SigningTable LogWhy Yes PidFile /var/run/opendkim/opendkim.pid Socket local:/var/spool/postfix/opendkim/opendkim.sock SyslogSuccess Yes TemporaryDirectory /var/tmp UserID opendkim:opendkim # Always oversign From (sign using actual From and a null From to prevent # malicious signatures header fields (From and/or others) between the signer # and the verifier. From is oversigned by default in the Debian package # because it is often the identity key used by reputation systems and thus # somewhat security sensitive. OversignHeaders From
Though most of the settings are fairly self-explanatory, it is a good idea to become familiar with the various settings to reduce the time spent troubleshooting why other mail servers are failing DKIM checks on mail sent from the server. One particular setting to note is SubDomains
(everything after the @ symbol in an email address) being set to no
. The above configuration has SubDomains
set to yes
as many popular PHP packages will default send email from the domain for which they are configured (e.g., wordpress@www.example.com).
Create the directory for the domain socket specified in opendkim.conf
, make the postfix
user a member of the opendkim
group so it can edit opendkim.sock
, and restart the service:
root@servername:~# mkdir /var/spool/postfix/opendkim root@servername:~# chown opendkim:root /var/spool/postfix/opendkim root@servername:~# service opendkim restart
Configure postfix
Following the below steps will configure postfix
exclusively for sending and will reject email not coming from one of the loopback addresses. Additionally, ufw
has not been configured to permit inbound connections commonly monitored by postfix
, so attempts to send mail to the server will fail to at least these two considerations.
Configure main.cf
The main.cf
file is the primary configuration file for Postfix, and it is enormous. Archive the original version, then create a new one:
root@servername:~# mv /etc/postfix/main.cf /etc/postfix/original.main.cf root@servername:~# nano /etc/postfix/main.cf
Add the following:
# See /usr/share/postfix/main.cf.dist for a commented, more complete version # Debian specific: Specifying a file name will cause the first # line of that file to be used as the name. The Debian default # is /etc/mailname. #myorigin = /etc/mailname smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu) biff = no # appending .domain is the MUA's job. append_dot_mydomain = no # Uncomment the next line to generate "delayed mail" warnings #delay_warning_time = 4h readme_directory = no # See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on # fresh installs. compatibility_level = 2 # TLS parameters smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key 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_security_level = may smtp_tls_note_starttls_offer = yes # See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for # information on enabling SSL in the smtp client. smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination myhostname = example.com alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases myorigin = /etc/mailname mydestination = relayhost = mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 mailbox_size_limit = 0 recipient_delimiter = + inet_interfaces = 127.0.0.1, [::1] inet_protocols = all non_smtpd_milters = unix:opendkim/opendkim.sock smtpd_milters = unix:opendkim/opendkim.sock
Configure master.cf
Another important and huge configuration file is master.cf
. Make an archive of the original version and make a new one:
root@servername:~# mv /etc/postfix/master.cf /etc/postfix/original.master.cf root@servername:~# nano /etc/postfix/master.cf
Add to the file:
# Postfix master process configuration file. For details on the format # of the file, see the master(5) manual page (command: "man 5 master" or # on-line: http://www.postfix.org/master.5.html). # # Do not forget to execute "postfix reload" after editing this file. # # ========================================================================== # service type private unpriv chroot wakeup maxproc command + args # (yes) (yes) (no) (never) (100) # ========================================================================== smtp inet n - y - - smtpd #smtp inet n - y - 1 postscreen #smtpd pass - - y - - smtpd #dnsblog unix - - y - 0 dnsblog #tlsproxy unix - - y - 0 tlsproxy #submission inet n - y - - 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 - y - - 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 #628 inet n - y - - qmqpd pickup unix n - y 60 1 pickup cleanup unix n - y - 0 cleanup qmgr unix n - n 300 1 qmgr #qmgr unix n - n 300 1 oqmgr tlsmgr unix - - y 1000? 1 tlsmgr rewrite unix - - y - - trivial-rewrite bounce unix - - y - 0 bounce defer unix - - y - 0 bounce trace unix - - y - 0 bounce verify unix - - y - 1 verify flush unix n - y 1000? 0 flush proxymap unix - - n - - proxymap proxywrite unix - - n - 1 proxymap smtp unix - - y - - smtp relay unix - - y - - smtp -o syslog_name=postfix/$service_name # -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 showq unix n - y - - showq error unix - - y - - error retry unix - - y - - error discard unix - - y - - discard local unix - n n - - local virtual unix - n n - - virtual lmtp unix - - y - - lmtp anvil unix - - y - 1 anvil scache unix - - y - 1 scache # # ==================================================================== # Interfaces to non-Postfix software. Be sure to examine the manual # pages of the non-Postfix software to find out what options it wants. # # Many of the following services use the Postfix pipe(8) delivery # agent. See the pipe(8) man page for information about ${recipient} # and other message envelope options. # ==================================================================== # # maildrop. See the Postfix MAILDROP_README file for details. # Also specify in main.cf: maildrop_destination_recipient_limit=1 # #maildrop unix - n n - - pipe # flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient} # # ==================================================================== # # Recent Cyrus versions can use the existing "lmtp" master.cf entry. # # Specify in cyrus.conf: # lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4 # # Specify in main.cf one or more of the following: # mailbox_transport = lmtp:inet:localhost # virtual_transport = lmtp:inet:localhost # # ==================================================================== # # Cyrus 2.1.5 (Amos Gouaux) # Also specify in main.cf: cyrus_destination_recipient_limit=1 # #cyrus unix - n n - - pipe # user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user} # # ==================================================================== # Old example of delivery via Cyrus. # #old-cyrus unix - n n - - pipe # flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user} # # ==================================================================== # # See the Postfix UUCP_README file for configuration details. # #uucp unix - n n - - pipe # flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient) # # Other external delivery methods. # #ifmail unix - n n - - pipe # flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient) #bsmtp unix - n n - - pipe # flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient #scalemail-backend unix - n n - 2 pipe # flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension} #mailman unix - n n - - pipe # flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py # ${nexthop} ${user}
Postfix user groups
The postfix
user will need to be added to the opendkim
group to support domain sockets:
root@servername:~# usermod -G opendkim postfix root@servername:~# service postfix restart
Configure DNS records
There are three different DNS record entries that can be made to reduce the chances of mail being declared as spam, the SPF record, DKIM record, and DMARC record.
Configure SPF records
SPF records are used to verify that emails originating from a domain are permitted to be sent on behalf of the domain. This means there is no actual local server configuration on a transactional mail server.
Log into your DNS server and create a text record with at least the following for the tld (e.g., example.com):
v=spf1 a -all
The above record will inform receiving servers that only the servers listed in the 'a' record (including 'aaaa' record for IPv6 addresses) are approved to send mail. The -all
mechanism means nothing else is approved to send mail. Note that the softfail mechanism (~
) used instead of the hardfail (-
) means the record will be ignored by most popular mail servers resulting in an increased chance of mail from the sending server arriving in spam folders.
If it is desired to send mail from a subdomain from the same server, it is necessary to create an SPF record in addition to the main domain. A simple solution is to use the include
mechanism. This mechanism means that the SPF record of the target domain will be included in the current domains SPF record.
Example for www.example.com:
v=spf1 include:example.com -all
Configure DKIM record
DKIM can only function with a valid DNS TXT record. In the DNS manager for your DNS server, make the following new TXT record:
Enter into the optional subdomain field:
selector._domainkey
Enter the contents of /etc/opendkim/keys/example.com/selector.txt
into the body of the record using the format below (i.e. only the contents between "
and "
):
v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuX4vPnLopTAIMFnnuP4CCEfE/FtQO0mi77voGsWSvHQfvFMIkQ3W3VmeAEiSJd6SVkL/Ojr30ag2i6wA3NTU+1ndfgL371zKx4gDAnewoRA4N2P05HPUNe10DE+m4xnwB6zsQnnPJ3EgKIW6W/v+fN/EzTfeJo5UmxiAoFRIq5hgyeHHCI8aKMQLCmWhb/Pz22MiqRHxV91xmTMLx/e3BIsplcOmQjlOyGagoIZJxpcTlf9OiSWks2a5kHXEN40eh99zkPGInqTrbhDog+cn/mvPgY0uIznx1i/ubRQFtYaH5t6vCu5uSMEQjcTQnWRLI9Qt7Mp15hOMrpkKv4SPzwIDAQAB
Keys can be verified using tools such as the one at DKIM Core. Note that some DNS servers may not like the length of the key string, so it may be necessary to use one of the record formatting methods recommended in the OpenDKIM README.
Configure DMARC record
DMARC records are used to inform receiving servers of what the domain owner's policies are for handling DKIM and SPF record checks. Similar to SPF records, there is no configuration for the local server.
Generating the DMARC record is easiest with an online tool, such as the Kitterman DMARC Record Assistant. The record generated there can be added to a DNS text record for the domain using the subdomain _dmarc.
Note that the quarantine
as a policy is much less substantial in preventing email from arriving in spam folders when compared to the reject
policy. However, with the reject
policy enforced, the email will not arrive when there is some sort of server configuration issue (e.g., opendkim
stops signing messages or a PHP package or plugin upgrade adds a previously unused subdomain to the email address).
The RUA and FUA reports must be sent to either an email address for the domain the record applies to (e.g., hostmaster@example.com) or if a different domain is used, then the DMARC record for that domain must state it is permitted to receive reports for the example.com domain, else the reports will not be sent.
A configuration for testing:
v=DMARC1; p=none; adkim=s; aspf=s
It may be informative to change from strict (=s
) to relaxed (=r
).
A simple, strict and brutal record that would not generate reports:
v=DMARC1; p=reject; adkim=s; aspf=s
Configure system messages
Receiving notifications from the system is a requirement for maintaining a server. For the purposes of this confirmation, the system message will be emailed by alias
@mailname
.
mailname
The system mailname
should be set to the domain of the server.
root@servername:~# nano /etc/mailname
Confirm the mailname matches the domain of the server. If the server is example.com, then use example.com
. If the server is subdomain.example.com, then use subdomain.example.com
.
For the purposes of this article, server example.com would have the following in /etc/mailname
:
example.com
alias
The alias
can be used to define the email address that messages for root
are sent to, using the /etc/aliases
file (see man 5 aliases
for more information).
root@servername:~# nano /etc/aliases
A basic server configuration for server example.com where the email address to receive system emails is username@example.net would look like:
# See man 5 aliases for format postmaster: root root: username@example.net
Some services, such as monit
, may require an additional entry:
# See man 5 aliases for format postmaster: root root: username@example.net monit: root
Additional measures to stay out of spam folders
Staying out of spam folders is relatively easy from a technical perspective. It is important to note that the very largest providers have made it nearly impossible to stay out of spam folders when not using one of the very largest providers to send mail from and resolving this issue is beyond the scope of this article. For the technical proof of work that the server is sending legitimate mail, perform the following in addition to the previous steps in this article:
Forward confirmed rDNS (FCrDNS)
Forward confirmed reverse DNS (FCrDNS) is simply a PTR record that in turn has an A record pointing to the IP address originally queried.
username@servername:~$ dig a example.com +short 192.0.2.1 username@servername:~$ dig -x 192.0.2.1 +short example.com
An online tool to confirm FCrDNS has been configured properly can be found at Forward Confirmed Reverse DNS Testing Form.
DNS Blacklists
DNS Blacklists come in many different flavors and may even have nothing to do with spam ever originating from an IP address, domain, or server. For the small enthusiast, the only action that can be taken is to verify nothing managed is on any blacklists and perform all steps necessary to be removed from blacklists, though this may occasionally require migrating to a new ISP.
It is more important to pay attention to blacklists employed by email server administrators than simply to remain off of all blacklists in general, as some of the blacklists exist purely to extort money from administrators.
Testing tools
Several tools exist for checking an IP address for blacklisting, including:
DNSBL Information - Spam Database and Blacklist Check - Thorough checking of lists for IP addresses.
MX Lookup Tool - Check your DNS MX Records online - MxToolbox - includes multiple different tools for checking different aspects of mail services.