Custom Mail-From with Amazon SES

We manage our own mail server; however, some big mail provider company (among other things), let’s name it the big M, has the very annoying habit of randomly blacklisting the entire IP range of our hosting provider because, maybe, some hosts within the DC are botnet-ed to the core. And we could ask to whitelist our IP, the entire block would get blacklisted again the following month, and that would take precedence.

Now since virtually everybody on the Internet uses either Outlook or Gmail as it’s email provider, that meant that at times a large chunk of our outbound emails would just get totally dropped, and we wouldn’t know about it until users complained.

Having given up with the big M since early 2000, I configured an Amazon SES relay for those problematic destinations. Our emails are relayed there when they need to reach an Outlook destination, and thankfully, they didn’t block AWS yet.

In addition, we also configured SPF and DKIM, so our domain doesn’t get wickedly used for spam. That works well when using our own SMTP, but when passing through the SES relay, the Mail-From/Return-Path/Bounce-Address would be rewritten as coming from {region}.amazonses.com. Hence, the relaxed SPF alignment check would fail. It requires that the Return-Path (rewritten to {region}.amazonses.com) be a subdomain or exact match of the From header (our own domain).

To remedy that, you need to have your domain registered as a verified identity in SES. Then in Amazon SES > Configuration: Identities > yourdomain, edit Custom MAIL FROM domain to a subdomain of yours. In our case, we used ses-relay.ourdomain, but it can be anything really. It just has to be a sub-domain.

You will also have to configure an MX record on that subdomain to bounce back to Amazon SES. The console gives you proper record to use, it looks like MX 10 feedback-smtp.{region}.amazonses.com. You also need to configure an SPF record on that subdomain to ensure that mail sent from the relay pass the SPF check. They recommend "v=spf1 include:amazonses.com ~all" for that, but we were a bit stricter and went with "v=spf1 include:{region}.amazonses.com -all".

You can choose the behavior should your MX on the subdomain be improperly configured or unreachable. Either it will reject the mail, or it will fallback to rewriting the Mail-From to the default of {region}.amazonses.com. I choose the former, because if it fails, I want it to fail hard. The latter, you would only find in your DMARC reports if you dutifully analyze all of them, all the time. That could be done if the DMARC reports were somehow automatically analyzed be we don’t do that yet.

Another thing that got me scratching my head for like an hour is that, in our case the custom Mail-From was not honored. Even though it was configured correctly in the console and the configuration marked as Successful, the source of the mail were still showing with a Return-Path under {region}.amazonses.com. The reason why was that we still had other SES identities that were used for testing, especially Email address identities. And seeing the matching email address, it would use those identities configuration instead of the domain identity configuration, thus ignoring the custom Mail-From. Actually found that out from this stackoverflow post.

Switch MTA on FreeBSD

As you probably know FreeBSD comes with Sendmail installed as the default MTA. However this may be a bit overkill on a desktop installation where the most you might want is to relay mails to an external address. Luckily it is quite easy to change the default MTA as described in the handbook, see 28.4. Changing the Mail Transfer Agent.

On my Desktop I prefer to install nullmailer. This is a simple MTA replacement for hosts which only relay mails through a smart relay. GNUTLS (SSL) is not enabled by default in the nullmailer package on FreeBSD. So if you want SSL you have to compile the port. This is my case. Let’s install it:

cd /usr/ports/mail/nullmailer
make install clean
(...)
pkg lock nullmailer

The configuration happens in /usr/local/etc/nullmailer. This directory contains multiple files and each one of them focuses on a specific aspect of the configuration.

First we specify the remote SMTP through which our mail shall be relayed, this is the remotes file. This file contains a list of remote servers, the module used to send the message and command-line arguments for that module. Modules are located in /usr/local/libexec/nullmailer. The man page states that you can list available options using --help on each protocol module.

In most cases you want to use the smtp module which takes the following arguments (with SSL enabled):

  • port: SMTP port (25, 465, 587, …)
  • user: SMTP user
  • pass: SMTP password
  • auth-login: LOGIN authentication method (default to PLAIN)
  • ssl: Use SSL/TLS encryption
  • starttls: Use STARTTLS command to initiate encrypted connection
  • insecure: Accept invalid certificates (which I do not recommend)
  • x509certfile: Client certificate file
  • x509cafile: Certificate Authority trust file (default to /etc/ssl/cert.pem on FreeBSD)
  • x509crlfile: Certificate revocation list
  • x509fmtder: Switch from PEM to DER format for the certificates

Here is an example that would relay through relay.example.com:465 using SSL and LOGIN authentication:

relay.example.com smtp --port=465 --ssl --auth-login --user=some-user --pass=some-password

Since this file contains your SMTP password in cleartext, I advise you to:

chown nullmail:nullmail remotes
chmod 600 remotes

Next we edit the name that will be used to construct email addresses on this host. You configure this in the me file. Normally this should be the fully-qualified host name of the computer running nullmailer. This is really useful to distinguish, say root at machine-a from root at machine-b. However some mail providers refuse to relay mails from a different domain name than their own so it might be useful to change this in those cases (I am my own mail provider, so personally I don’t care and do what I want). You also need to configure defaultdomain to your domain name. That is your FQHN minus the hostname. If a mail is sent to an address that is not localhost and does not contain a domain name (no period in the hostname), this domainname will be appended to it.

After that we configure the mail to which all local mails are forwarded. You configure this address in the adminaddr file. And we also configure the file pausetime. This is the interval of time between two queue runs with a default value of 60 seconds. I prefer to set this to a higher value, like 15 minutes.

For more information about the configuration of nullmailer, see this article. Although related to Raspbian on a RPi, it remains mostly the same.

Now we need to replace the MTA on FreeBSD. First we configure the mailwrapper (see man mailwrapper) in /etc/mail/mailer.conf. Replace each line with their nullmailer equivalent, that is:

sendmail  /usr/local/libexec/nullmailer/sendmail
send-mail /usr/local/libexec/nullmailer/sendmail
mailq     /usr/local/libexec/nullmailer/mailq

Time to test. Disable sendmail, enable nullmailer and send a mail. Oh and by the way, tail -f /var/log/maillog in any case:

service sendmail stop
service nullmailer onestart
echo Hello from FreeBSD\! | mailx -s "test" root

If it works, you can now disable sendmail and enable nullmailer in /etc/rc.conf:

sendmail_enable="NONE"
nullmailer_enable="YES"