"Contact me" forms are pretty common. You can use it for your portfolio website, contact section of your online business and so on.
This tutorial assumes our "Contact me" form has three fields: Name, Email, and Message. On hitting submit it will call a REST API which will trigger our GCP (Google Cloud Platform) function which in turn will forward above mentioned details to our email.
This tutorial will cover the following things:
- Setting up the code with "Serverless Framework" in Golang.
- Configuring Twilio SendGrid API.
- Prepare GCP resources.
- Deploying the Golang code on Google Cloud Functions.
Setting up the code
We are using the "Serverless Framework" to manage our function. In this tutorial, we will configure it to support GCP. But it's very easy to change the provider to other platforms like AWS, Azure, etc.
If you don't have "Serverless Framework" installed on your machine, let's get it first by following below link (just install it, no need to login):
https://serverless.com/framework/docs/getting-started/
Create the codebase:
$ cd to/desired/location/in/GOPATH
$ serverless create --template google-go --name contactmebackend --path contactmebackend
Next, install the plugin required for GCP (I'm using yarn, feel free to use NPM):
yarn add serverless-google-cloudfunctions
You will get the following files under contactmebackend
the directory:
Makefile
fn.go
fn_test.go
node_modules
package.json
serverless.yml
yarn.lock
We will use Twilio's SendGrid API to send the information coming from REST API to our email address. SendGrid has a Golang package to interact with its API. Since we will be deploying this code to GCP functions later, we will need to setup Golang module so that GCP can figure out the external dependencies. Follow these steps to do just that:
$ export GO111MODULE=on
$ go mod init
$ go get -u github.com/sendgrid/sendgrid-go
Edit fn.go
with the following code. I have provided comments to better understand the code (don't forget to replace it with your name and email id):
package contactmebackend
import (
"encoding/json"
"log"
"net/http"
"os"
"github.com/sendgrid/sendgrid-go"
"github.com/sendgrid/sendgrid-go/helpers/mail"
)
// Email struct defines the data coming via REST API
type email struct {
EmailAddress string `json:"emailAddress"`
Name string `json:"name"`
Message string `json:"message"`
}
// SendEmail function sents email with the data coming from REST API
func SendEmail(w http.ResponseWriter, r *http.Request) {
var e email
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&e)
if err != nil {
panic(err)
}
// Prepare the email content
from := mail.NewEmail(e.Name, e.EmailAddress)
subject := "Contacted via portfolio website"
to := mail.NewEmail("Your name", "Your email id")
plainTextContent := e.Message
message := mail.NewSingleEmail(from, subject, to, plainTextContent, plainTextContent)
// Get the SendGrid API key from environment variable
sendGridAPIKey := os.Getenv("SENDGRID_API_KEY")
// Generate a SendGrid Send Client
client := sendgrid.NewSendClient(sendGridAPIKey)
// Send the message
response, err := client.Send(message)
if response.StatusCode != http.StatusAccepted {
if response.StatusCode != http.StatusOK {
// Request failed
log.Println(err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("Something bad happened!"))
}
} else {
// Request successful
// Setting header to allow cors
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write([]byte("Success"))
}
}
Configuring Twilio SendGrid API
The only thing remaining for the above code to work as expected is the SendGrid API key. To get the key, log in to SendGrid Console first. Then go to create API key page. Click on Create API Key.
Enter the name for the key. You can keep the Full Access for API Key permissions and hit "Create & View". Copy the API key displayed on the next page and keep it with you in some secure place.
Testing the code
To make sure the above code works, we will edit fn_test.go
and run tests. Edit fn_test.go
with the following code:
package contactmebackend
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestSendEmail(t *testing.T) {
// Mock data with the 'body' which will go over the REST, and 'wantStatus' which is the expected status for the data sent in 'body' field
tests := []struct {
body string
wantStatus int
}{
// This one has all the necessary fields for successful request
{body: `{"name": "Itachi Redhair", "emailAddress": "itachi.redhair@gmail.com", "message": "Would love to meet over a beer!"}`, wantStatus: http.StatusOK},
// This one will fail the request because emailAddress is missing.
{body: `{"name": "Itachi Redhair", "message": "Would love to meet over a beer!"}`, wantStatus: http.StatusInternalServerError},
}
for _, test := range tests {
// httptest.NewRequest package will form the mock request object which we can pass on to our function directly
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(test.body))
req.Header.Add("Content-Type", "application/json")
// mock response object
rr := httptest.NewRecorder()
SendEmail(rr, req)
if got := rr.Result().StatusCode; got != test.wantStatus {
t.Errorf("SendEmail(%q) = %q, want %q", test.body, got, test.wantStatus)
}
}
}
Now before you run the tests, you need to set the environment variables. Run the following command with the SendGrid API key:
export SENDGRID_API_KEY={Paste the api key which you copied earlier}
Run the test with the following command:
go test
You should see output which should be somewhat like this:
2020/01/08 11:18:29 <nil>
PASS
ok medium-code-dump/contactmebackend 1.003s
Preparing GCP resources
Now that our code is ready, we need to deploy it. To deploy it on Cloud Functions we need to enable few GCP APIs. You can do so by following this guide on serverless.com.
Once you've enabled the required APIs, got credentials JSON file, stored it on your local machine and updated the serverless.yml
file, edit functions
field in serverless.yml
field like below:
functions:
first:
handler: SendEmail
events:
- http: dummy-text
SendEmail
is the name of the handler function from our code in fn.go
. Name of the function first
doesn't really mean anything and is ignored, same goes for value of the field http
. You can see this link for details.
Deploying
We are just there. To deploy the code just hit the following command:
serverless deploy
Wait till the deployment is done. You will get the link to trigger the function at the end. Something like this:
Deployed functions
first
https://us-central1-{project-id}.cloudfunctions.net/SendEmail
Before you try the above link, you need to specify the environment variable SENDGRID_API_KEY
in your cloud functions.
Go to Cloud Functions List in GCP console. Open our newly deployed function SendEmail
. Click on edit. Scroll down and you will see an option to edit the environment variables. Add SENDGRID_API_KEY
and it's value and hit deploy.
After your function is deployed, try the following command with the above link:
curl -X POST \
https://us-central1-{project-id}.cloudfunctions.net/SendEmail \
-H 'Content-Type: application/json' \
-d '{
"emailAddress":"dummy-email@dummy.com","name":"dummy name", "message":"Hi there would like to meet you!"
}'
Check your email and if everything goes fine you won't be disappointed.
Wrapping up
This is a very simple and minimal example of how you can leverage Google Cloud Functions to deploy serverless service written in Golang.
I am using this for the contact me form I have on my portfolio and wanted to share with you the approach I've followed. If you need a live demo, then check out my portfolio website https://findakshay.dev and scroll down.
If there is something you didn't understand or not working for you or can be improved, please let me know. You can reach out to me via Twitter or Linkedin. You can also email me at akshay.milmile@gmail.com. You can check out some other projects that I've worked in past on my Github.
So that is it guys. Thank you for reading the article. Wish you a happy new year. Peace out ✌.
Top comments (0)