I have a very simple website hosted on a GCP storage segment and now I need to make it slightly less simple by adding a contact form. Then I realized that the easiest was to use everything I already have more or less in place: I could write a Function using the Google Cloud Platform that would send the emails using the GSuite account I use for the same domain. Let's see how everything goes!

The set-up

We need to use the OAuth2 authentication method, and for that to work we first need to go to the gcp console and select (or create!) a service account. Once this is done, click on the More button (the three dots) and then click Create key, this will take you to the process of creating a service key and will download a JSON file with all the data we need.

Now, we have to go to the GSuite control panel and enable de API for the service account. Go to Security -> Advanced security -> Manage API client access and enter your client_id and https://mail.google.com/ for the API scope. You can find your client_id in the JSON file.

The Google Cloud Function

To create a Google Cloud Function, we have to go to the Functions Overview page in the gcp console, click on Create function and give it a name. Then we need to select the HTTP Trigger and the Node runtime. You would also probably want to select the lower memory allocation possible. For now, we will use the inline editor, so we will later need to paste the contents of our index.js and package.json there. We need to set the Function to execute, sendMail in my case; and then we can click on more to set some environment variables. Our code will use the following:

GMAIL_ADDRESS: This is the user that we will use for authenticating, bear in mind it has to be a real user and not an alias.
CLIENT_ID: found in the JSON file.
PRIVATE_KEY: found in the JSON file.

We will also set MAIL_FROM, MAIL_TO and MAIL_BCC; these can be sent in the request but we want to have a fallback as they won't be mandatory.

The code

The only dependency we are going to have is Nodemailer, so:
npm i --S nodemailer

Now, let's take a look at the code:

exports.sendMail = (req, res) => {}

For this function to work, our sendMail method will provide two arguments: the request and the response.

  if (!req.body.subject || !req.body.text) {
    res.status(422).send({
      error: {
        code: 422,
        message: "Missing arguments"
      }
    });
    return;
  }

First thing we do is check if we have everything we need to carry on, in my case I only care if I have an actual email to send, other parameters like from or to are optional as I will store environment variables for them, but you can check for whatever you need to!

const nodeMailer = require("nodemailer");

const transporter = nodeMailer.createTransport({
  host: "smtp.gmail.com",
  port: 465,
  secure: true,
  auth: {
    type: "OAuth2",
    user: process.env.GMAIL_ADDRESS,
    serviceClient: process.env.CLIENT_ID,
    privateKey: process.env.PRIVATE_KEY.replace(/\\n/g, "\n")
  }
});

Now we create a transport with our configuration. It will take the values from the environment variables we previously defined. Note that we are doing a string replacement as the platform would have escaped the \n in our key, if we don't do this the private key won't be valid.

const mailOptions = {
  from: req.body.from || process.env.MAIL_FROM,
  to: req.body.to || process.env.MAIL_TO,
  bcc: req.body.bcc || process.env.MAIL_BCC,
  subject: req.body.subject,
  text: req.body.text
};

Now we define our mail options, again, these may be different from what you need, you can check the documentation for more detail on what's available.

transporter
    .sendMail(mailOptions)
    .then(() => {
      res.status(200).send({
        data: {
          code: 200,
          message: "Mail sent"
        }
      });
    })
    .catch(e => {
      res.status(500).send({
        error: {
          code: 500,
          message: e.toString()
        }
      });
    });

Finally, we try to send the email and return the appropriate response. You can check the full code on github

Syncing your repo

You probably noticed that there are several options to upload the code to your Function, one of them is attaching a Cloud Source repository... and that's great because then you can maintain your function just by pushing your code to the repo. My problem is I like to use GitHub, and that's not an option here... But we can mirror our repos!

We need to add a new Cloud Source repository on Google Source Repositories. Click on Add repository and select Connect external repository. Then, select the project where you have your Function and choose your connector (GitHub or Bitbucket). Next thing to do is authorize Cloud Source Repository to store your credentials, follow the steps to connect with your account and select the repo you want to associate with your Cloud Source repo. The name of the repository will have github_ or bitbucket added to the beginning of its name, and will automatically in sync with your original repository.

Once we have our Cloud Source repository ready, we can go back to our Function, click edit and select Cloud Source Repository as source: fill in the repository name and the branch or tag you want to link, and you are ready to go!

Logo

云原生社区为您提供最前沿的新闻资讯和知识内容

更多推荐