Linux desktop Postfix queue for Gmail SMTP
April 30, 2019
On a Linux desktop, I want to start sending email through Gmail in a G Suite account using SMTP, rather than a self-hosted SMTP server. Since Gmail supports SMTP, that should be easy enough.
Google’s article Send email from a printer, scanner, or app gives an overview of several options. I’ll choose the “Gmail SMTP server” track, which seems designed for individual user cases like this.
However, since I am using two-factor authentication (2FA) on this Google account — as we should all be doing now for all accounts wherever possible! — my Gmail login won’t work for SMTP because the clients I am using don’t have a way to supply the 2FA time-based token.
Google’s solution to this is to have me generate a separate “App Password” that can sidestep 2FA for this limited purpose: Set up an App Password.
That works fine, but the app password is a randomly-generated 16-letter password that is not amenable to being memorized. For security reasons, my mail client doesn’t cache passwords between sessions, so I have to look it up and enter it each time I start the mail client. That’s generally only once per day for me, so it’s not a big problem, but it would be nice to avoid.
I also want other local programs — such as cron jobs, development projects underway, etc. — to be able to send mail out through my Gmail account. How can I do that, ideally without teaching each one separately how to do it?
As a server operating system at heart, Linux of course has many SMTP servers that can intermediate by acting as a local SMTP server, queue, and sending client. Such a server could have my Gmail password configured and stored under a separate user account, giving a bit more isolation from my main desktop user.
What local SMTP program to use?
I first tried using the lightweight and ephemeral
esmtp since I had already used it on my desktop computer to forward email through an SSH tunnel. I wasn’t able to get it working with Gmail, which could easily have been operator error on my part.
Before trying much to solve the problem, I realized that I really would like a local queue for outgoing email so I don’t have to wait for my mail client to connect and send each message before I can get on with more email. Given how much email I handle each day, even fairly brief delays add unwanted drag.
I used another similar program called
ssmtp a long time ago, and considered trying that again.
Then I read that ssmtp is unmaintained and does not validate TLS certificates, negating some of the security value of using TLS in the first place. So, no to that.
I next saw a few people mention that
E-MailRelay is a nice option to locally queue and forward email. But looking at a new and more comprehensive email daemon like that, I realized I would likely find it easier to just use an SMTP server I already know, such as Exim, Sendmail, or …
Postfix is one of the most widely-deployed mail servers. I already know it well and have used it for many years. It has most every option I would ever want. So I’ll use that.
This seems like a slight bit of overkill for just sending outgoing email, but Postfix is battle-hardened and comparatively lightweight, using around 30 MiB resident RAM between its 4 daemon processes on my computer. Yes, that is laughably bloated by the standards of yore, but svelte compared to even a single tab in a modern graphical browser.
(You’ll need to be root to do the following setup.)
First, install Postfix as appropriate for your Linux distribution:
# dnf install postfix # Fedora # yum install postfix # CentOS/RHEL # apt install postfix # Debian/Ubuntu
Now, time to edit the default Postfix configuration. The options are all well-documented on the Postfix website but there are a lot of them and it can take a while to figure out what you need and want.
Here is what I added to
relayhost = [smtp.gmail.com]:465 smtp_tls_wrappermode = yes smtp_tls_security_level = verify smtp_tls_mandatory_protocols = !SSLv2, !TLSv1, !TLSv1.1 tls_high_cipherlist = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256 smtp_tls_mandatory_ciphers = high smtp_tls_loglevel = 2 smtp_sasl_auth_enable = yes smtp_sasl_mechanism_filter = plain smtp_sasl_password_maps = hash:/etc/postfix/smtp_auth smtp_sasl_security_options = noanonymous header_checks = regexp:/etc/postfix/header_checks smtp_address_preference = ipv6
Let’s go through each of those settings:
relayhost we define the remote server we want to route all our outgoing mail through. The
[...] around the hostname disables MX lookups, since there is no MX record for smtp.gmail.com. Port 465 is used for TLS-wrapped (called “implicit”) mail submission, so we also need to set
I used the port number 465 rather than the name
smtps because the port has been reassigned for another purpose, so it may be mentioned in the
/etc/services file under either the old or new names or both, and isn’t worth risking breakage for.
smtp_tls_wrappermode means we initiate a TLS connection immediately, rather than first connecting with classic plain-text SMTP protocol, and then requesting a switch to TLS mode with the
STARTTLS SMTP verb. This option is why we specified port 465 above for our
Like me, you may recall the option for SSL-wrapped SMTP to port 465 as little more than an odd Microsoft Exchange and Outlook convention from years ago. Once upon a time it was. Why not use the more standard submission port 587? Up until fairly recently that would have been best.
In a funny turn of events, nowadays TLS-wrapped SMTP on port 465 is recommended as the best way for end-users to submit mail! See RFC 8314 for details. I found the helpful FastMail explanation of all the historical twists well worth reading. As they say, the best thing about standards is that there are so many to choose from. 😆
verify means that we want TLS to be mandatory for every connection, and we want Postfix to validate that the certificate name matches the hostname we are using to connect to prevent man-in-the-middle (MITM) attacks.
smtp_tls_mandatory_protocols we are disabling all older TLS protocols, allowing only the most current (as of this writing) versions 1.2 and 1.3. That would be overly restrictive and unsafe to do on a public mail server, but since we are only talking to a single mail server here, and we know that Google supports modern TLS, we can restrict ourselves to that.
tls_high_cipherlist list is our restricted set of TLS ciphers in our preferred order. Again, this would be unwise to do on a public mail server, but in our role as a client forwarding to only one destination here, it is a good thing. My list comes from Mozilla’s modern TLS recommendation.
smtp_tls_mandatory_ciphers set to
high, we ensure the list we just specified gets used.
smtp_tls_loglevel to 2 so that the Postfix logs will show helpful details about TLS connection negotiations and results.
The next several
smtp_sasl_ options configure our use of a username and password. (SASL means “Simple Authentication and Security Layer”.) Note the
smtp_sasl_password_maps file we specified. The file name is arbitrary. Let’s create that now in
Of course substitute your own email address and Gmail app password there.
We need to create a fast binary map equivalent of that file for Postfix to read, and since it contains a password that should be kept private, let’s make it unreadable by other users on the system:
# postmap hash:smtp_auth # chmod go= smtp_auth*
Next I set option
header_checks to look for regular expressions in another file we need to create,
/^Received:\ (from|by)\ .*(yourhostname|localhost|localdomain)/ IGNORE
That removes a pair of headers that Postfix normally adds to each message we send, tracking the receipt and forwarding on of the email:
Received: by yourhostname.localdomain (Postfix, from userid 1000) id F2F7111C64A1; Tue, 30 Apr 2019 17:36:12 -0600 (MDT) Received: from localhost (localhost [127.0.0.1]) by yourhostname.localdomain (Postfix) with ESMTP id EE7DB11C161C; Tue, 30 Apr 2019 17:36:12 -0600 (MDT)
In our case those are a waste of space because it is not interesting to track the flow of email around localhost. So we just remove them before sending the mail on.
Finally, a minor nicety I like to enable is to set
ipv6 so that when we have an IPv6 connection to the outside world, that is used to send the mail. An IPv4 connection will still be used if IPv6 isn’t available. I figure we’d might as well use the newer routing tubes of the Internet when we can.
Trying it out
Now we’re ready to start Postfix, and set it to start automatically at boot time:
# systemctl start postfix # systemctl enable postfix
Whatever it is, set your mail client to send mail through local Postfix executable
/usr/sbin/sendmail (the default for many), or through SMTP to
localhost:25. For me, that meant editing
~/.pinerc. When I was sending mail directly from Pine to Gmail, it contained:
But now I want there to be no setting so it falls back to
Now watch your Postfix logs, usually in
/var/log/mail.log on Debian and Ubuntu systems, and
/var/log/maillog on Fedora and CentOS.
And send a message!
Let’s look at what Postfix logged when I sent a test message.
When using rsyslog my complete log lines look like this:
2019-04-30T17:59:11.887004-06:00 localhost postfix/smtpd: connect from localhost[127.0.0.1]
That timestamp and hostname prefix waste a lot of room on each line here, so I will trim them from the rest of the xample:
postfix/smtpd: connect from localhost[127.0.0.1] postfix/smtpd: DA75311C649C: client=localhost[127.0.0.1] postfix/smtpd: disconnect from localhost[127.0.0.1] ehlo=1 mail=1 rcpt=1 data=1 quit=1 commands=5 postfix/pickup: DE8FE11C64A1: uid=1000 from=<firstname.lastname@example.org> postfix/cleanup: DE8FE11C64A1: message-id=<f15302b5-cd03-488a-a401-e1eb8544895f@ybpnyubfg> postfix/qmgr: DE8FE11C64A1: from=<email@example.com>, size=393, nrcpt=1 (queue active) postfix/smtp: initializing the client-side TLS engine postfix/smtp: setting up TLS connection to smtp.gmail.com[2607:f8b0:4001:c03::6c]:465 postfix/smtp: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: TLS cipher list "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:!aNULL" postfix/smtp: SSL_connect:before SSL initialization postfix/smtp: SSL_connect:SSLv3/TLS write client hello postfix/smtp: SSL_connect:SSLv3/TLS write client hello postfix/smtp: SSL_connect:SSLv3/TLS read server hello postfix/smtp: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: depth=2 verify=1 subject=/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign postfix/smtp: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: depth=1 verify=1 subject=/C=US/O=Google Trust Services/CN=Google Internet Authority G3 postfix/smtp: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: depth=0 verify=1 subject=/C=US/ST=California/L=Mountain View/O=Google LLC/CN=smtp.gmail.com postfix/smtp: SSL_connect:SSLv3/TLS read server certificate postfix/smtp: SSL_connect:SSLv3/TLS read server key exchange postfix/smtp: SSL_connect:SSLv3/TLS read server done postfix/smtp: SSL_connect:SSLv3/TLS write client key exchange postfix/smtp: SSL_connect:SSLv3/TLS write change cipher spec postfix/smtp: SSL_connect:SSLv3/TLS write finished postfix/smtp: SSL_connect:SSLv3/TLS write finished postfix/smtp: SSL_connect:SSLv3/TLS read server session ticket postfix/smtp: SSL_connect:SSLv3/TLS read change cipher spec postfix/smtp: SSL_connect:SSLv3/TLS read finished postfix/smtp: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: Matched subjectAltName: smtp.gmail.com postfix/smtp: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465 CommonName smtp.gmail.com postfix/smtp: smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: subject_CN=smtp.gmail.com, issuer_CN=Google Internet Authority G3, fingerprint=DF:CB:AA:81:ED:77:D4:BE:E5:47:6F:0E:A3:44:99:BA, pkey_fingerprint=EE:0F:3A:CC:6E:4E:EB:C0:1D:88:B6:73:BD:42:C4:83 postfix/smtp: Verified TLS connection established to smtp.gmail.com[2607:f8b0:4001:c03::6c]:465: TLSv1.2 with cipher ECDHE-RSA-CHACHA20-POLY1305 (256/256 bits) postfix/smtp: DE8FE11C64A1: to=<firstname.lastname@example.org>, relay=smtp.gmail.com[2607:f8b0:4001:c03::6c]:465, delay=1.3, delays=0.03/0.03/0.52/0.75, dsn=2.0.0, status=sent (250 2.0.0 OK 1556668753 y199sm14693187iof.88 - gsmtp) postfix/qmgr: DE8FE11C64A1: removed
We can see that the TLS connection was made, negotiated over IPv6 with TLS 1.2 and a nice modern cipher, and the certificate matches the hostname we used. Then the email was sent.
Google has some of their SMTP servers offering the newer, unfinalized TLS 1.3 protocol, since I see that about half the time:
postfix/smtp: Verified TLS connection established to smtp.gmail.com[2607:f8b0:4001:c06::6d]:465: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
And I received my test mail on the other end.
Now I can use any local mail client without having to separately configure each one to send outgoing mail to Gmail, and without having to enter the Gmail app password each time I start my mail client.
Sending email is immediate since the client only waits for the fast local queueing to complete, and Postfix forwards the mail in the background.
Remember to check your logs to make sure your mail is getting delivered, since your client will no longer know if it is not. If nobody replies to you for too many hours, check the logs first! Or set up something to monitor your logs for errors and alert you.
This approach is also useful on servers that need to send application email from a Gmail account, such as when you want mail to have a From: header with a @gmail.com or G Suite domain address.
Thanks, open source community, for the good software and documentation and open standards!