As of
Est. 

Enable Outbound E-mail on GCP

How to sent outbound mail from GCP using SendGrid. 📤

The first thing I wanted to do with my GCP VM was to install some blog software. I had the old-fashioned notion that if I wanted to offer a blog on the web, I had to join a blog site like Blogger.com or set up my own blog on my own server using WordPress or something similar.

Cloud    Server    Blogging Software    Blog    

At the time, that made sense to me. Today, I run Ergberg's Jotter using VitePress on Cloudflare Pages.

I don't remember which version of WordPress I tried at the time, but I do remember that the installer wanted to send e-mails about administrative events, all of which ended up in the dead letter queue.

Egress Traffic to Port 25 is Blocked

So I learned that by default Google blocks all TCP connections that leave the cloud platform and try to connect to port 25 in the outside world. The reason is simple to understand: Google doesn't want to be the turbo for spammers. Of course, there are also exceptions to this rule. As a selected customer, you can ask Google to exempt you from this restriction, but the blocking is set by default.

Sending E-mail from Within GCP

There are several solutions to get around the restriction. Basically, you need to find an e-mail service to forward your e-mails to. This can be Google Workspace or a trusted third-party mail provider. I chose SendGrid as the provider through which I route my e-mail traffic. Not only do they offer a free plan that is perfectly adequate for my needs. GCP also provides step-by-step instructions on how to set it up. In addition to SendGrid, GCP equally supports Mailjet and Mailgun. I chose purely based on the names, preferring the least militant.

Config: Sending via SendGrid

So I created an account with SendGrid and generated an API key. I installed mailutils (which includes postfix) on my GCP box. Google suggests to install postfix with the Local Only config, remove default_transport = error and relay_transport = error in /etc/postfix/main.cf and add the following lines there:

diff
< default_transport = error
< relay_transport = error
---
> relayhost = [smtp.sendgrid.net]:2525
> smtp_tls_security_level = encrypt
> smtp_sasl_auth_enable = yes
> smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
> header_size_limit = 4096000
> smtp_sasl_security_options = noanonymous
< default_transport = error
< relay_transport = error
---
> relayhost = [smtp.sendgrid.net]:2525
> smtp_tls_security_level = encrypt
> smtp_sasl_auth_enable = yes
> smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
> header_size_limit = 4096000
> smtp_sasl_security_options = noanonymous

Here the API key is stored in /etc/postfix/sasl_passwd.db as follows (assuming api_key variable holds the key):

bash
echo "[smtp.sendgrid.net]:2525 apikey:${api_key}" > \
          /etc/postfix/sasl_passwd
postmap   /etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd.db
rm        /etc/postfix/sasl_passwd
echo "[smtp.sendgrid.net]:2525 apikey:${api_key}" > \
          /etc/postfix/sasl_passwd
postmap   /etc/postfix/sasl_passwd
chmod 600 /etc/postfix/sasl_passwd.db
rm        /etc/postfix/sasl_passwd

In addition to the suggestions from Google, I also made the following settings

diff
> masquerade_domains = ergberg.tk
> sender_canonical_maps = regexp:/etc/postfix/sender_canonical_maps
> masquerade_domains = ergberg.tk
> sender_canonical_maps = regexp:/etc/postfix/sender_canonical_maps

masquerade_domains maps subdomains to ergberg.tk. The substitutions in /etc/postfix/sender_canonical_maps map senders of my GCP domain xx@.internal to xx@ergberg.tk.

Receiving E-mail for a Domain

The Local Only configuration is sufficient to send mails and fulfills my original requirement. But since I just set up Postfix on my GCP box: Why not go all the way and set up mail.ergberg.tk as mailhost for ergberg.tk?

That's a longer trip:

1. Send Only2. Domain Name3. MX Record4. Certificate5. Send & Receive

Additional tasks to enable receiving
  1. I'll start with the Send Only configuration above.
  2. I have an internet domain name, so that mail can be sent to user@ergberg.tk.
  3. This domain needs an DNS MX record so others can find the mail host.
  4. To get rid of the snake oil in main.cf, I requested a TLS certificate for mail.ergberg.tk
  5. Finally, I completed the main.cf configuration, see next section.

Config: Receiving Mail

The main additional change in /etc/postfix/main.cf for receiving e-mail is listening on some/all network interfaces. Changing loopback-only to all is equivalent to deleting the inet_interfaces line, since all is the default. So instead of applying the diff, you can just delete the line.

diff
< inet_interfaces = loopback-only
---
> inet_interfaces = all
< inet_interfaces = loopback-only
---
> inet_interfaces = all

I also replaced the Snakeoil with the Let's Encrypt certificate, which would also make sense for Send Only variant.

diff
< smtp_tls_security_level=may
< smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
< smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
---
> smtp_tls_security_level=encrypt
> smtpd_tls_cert_file=/etc/letsencrypt/live/ergberg.tk/fullchain.pem
> smtpd_tls_key_file=/etc/letsencrypt/live/ergberg.tk/privkey.pem
< smtp_tls_security_level=may
< smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
< smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
---
> smtp_tls_security_level=encrypt
> smtpd_tls_cert_file=/etc/letsencrypt/live/ergberg.tk/fullchain.pem
> smtpd_tls_key_file=/etc/letsencrypt/live/ergberg.tk/privkey.pem