Sending mail from Cloud Build

A simple solution for sending basic emails from a GCP Cloud Build pipeline

Jonathan Merlevede
datamindedbe
4 min readNov 19, 2021

--

Photo by Rinck Content Studio on Unsplash

This short practical story shows how to create a container for sending mail through a SMTP relay server using msmtprc using mutt, and how to call on this container to send an email as part of a Cloud Build pipeline. We prevent embedding your password to the relay by storing it in Secret Manager using Build’s relatively new support for mounting secrets as environment variables.

You can create the described resources from the Cloud Console, the gcloud CLI, Terraform, Pulumi, Google Cloud Deployment Manager or something else; whichever is the most appropriate for the environment you are working in. If you are not using infrastructure as code (IaC) and are setting up infrastructure that you will have to maintain, consider changing your ways :).

SMTP relay

GCP does not have a service like Amazon Simple Email Service (SES) that allows sending emails. This unfortunately implies that it is not easy to use your cloud builder’s identity (service account) to send mail.

Here, I will simply authenticate with an SMTP relay service using a username and password. This is also the official solution suggested by Google. The relay service can be your Workspace’s Gmail, Amazon SES or another third-party mail service like Mailjet.

Set up artifact registry

Enable the artifact registry API and Start by creating a Docker repository; I called mine builders (you can use container registry too). For the purposes of this short story, I’ll assume the Docker repository is at $MYREPO:

MYREPO=europe-west1-docker.pkg.dev/$CLOUDSDK_CORE_PROJECT/builders

where $CLOUDSDK_CORE_PROJECT is the name of your GCP project. Make sure to enable access to the repository:

gcloud auth configure-docker europe-west1-docker.pkg.dev

Create the container

For the purposes of this short story I will simply build the image locally. You can of course use Cloud Build to build the image instead. Start by creating a msmtp configuration file, msmtprc. The following configuration will allow connecting to Gmail:

account default
host smtp.gmail.com
port 587
tls on
tls_starttls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
tls_certcheck on
auth on
user john.doe@mydomain.com
passwordeval "echo $MSMTP_PASSWORD"
from john.doe@mydomain.com

Change these settings as appropriate. This page shows some alternative configurations.

This config tells MSMTP to read the password from an environment variable called MSMTP_PASSWORD. In Cloud Build we will mount this variable from Secret Manager.

Then, create a simple Dockerfile :

FROM alpine:latest
RUN apk add mutt msmtp ln -sf /usr/bin/msmtp /usr/sbin/sendmail
ADD msmtprc /etc/msmtprc
  • Start from Alpine to obtain a small image
  • Install mutt and msmtp
  • Add the msmtprc configuration file you created above

Build your image:

docker build -t mymailer -t $MYREPO/mymailer .

Lastly, push the image to the Docker repository:

docker push $MYREPO/mymailer

Note that instead of creating your own small image, you could also use one of the many images that exist on Docker Hub or Github that allow configuration through environment variables. Reply in the comments if you found one that you’re happy with!

Define your secrets

Enable the Secret Manager API and create a secret msmtp_password containing the password to your SMTP relay server. Note down the secret’s resource ID, as you will need to enter it in your cloudbuild.yaml. It looks something like this:

projects/$PROJECT_NUMBER/secrets/msmtp_password/versions/latest

Configure Cloud Build

You have to give Cloud Build access to your secret. The easiest way to do this is to navigate to Cloud Build in the console and assign it the Secret Manager Secret Accessor role. If you have other secrets defined in your project, you may want to look into scoping permissions more granularly, for example by running Cloud Build using a user-specified custom service account; this falls outside of the scope of this story.

Assign the Secret Manager Secret Accessor role to the default Cloud Build service account

Mail from your build pipeline

Now add a step to your cloudbuild.yaml file that will send an email:

steps:
- name: europe-west1-docker.pkg.dev/[...]/mymailer:latest
args:
- '/bin/sh'
- '-c'
- |
echo $$MSTP_PASSWORD > /dev/null
echo "body" | mutt -s "subj" -a att.file -- dest@mail.com
secretEnv: [MSMTP_PASSWORD]
availableSecrets:
secretManager:
- versionName: projects/[...]/msmtp_password/versions/latest
env: MSMTP_PASSWORD

Adapt the configuration as appropriate; the […] signs are there just so the code fits on a single line. This example sends a plain text mail with subject subj and contents body to dest@mail.com, that has the file att.file attached to it.

The line echo $$MSTP_PASSWORD > /dev/null is there because at the time of writing Cloud Build unfortunately refuses to run if the declared secrets are not explicitly referenced from the cloudbuild.yaml file.

You can now send mail from Cloud Build!

--

--