Skip to content

Commit 4c0ef15

Browse files
thomas-tacquetShillakerBemilie
authored
TEM x Functions tutorial (#68)
* TEM x Functions tutorial * update base readme * update base readme * Apply suggestions from code review Co-authored-by: Simon Shillaker <[email protected]> Co-authored-by: Emilie BOUIN <[email protected]> * review * cleanup deps --------- Co-authored-by: Simon Shillaker <[email protected]> Co-authored-by: Emilie BOUIN <[email protected]>
1 parent fd82634 commit 4c0ef15

File tree

8 files changed

+298
-1
lines changed

8 files changed

+298
-1
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Table of Contents:
1818
- [Examples](#examples)
1919
- [🚀 Functions](#-functions)
2020
- [📦 Containers](#-containers)
21+
- [⚙️ Jobs](#️-jobs)
2122
- [💜 Projects](#-projects)
2223
- [Contributing](#contributing)
2324

@@ -54,6 +55,7 @@ Table of Contents:
5455
| **[Triggers NATS](functions/triggers-nats/README.md)** <br/> Simple NATS trigger example using Terraform. | all | [Terraform] |
5556
| **[Typescript with Node runtime](functions/typescript-with-node/README.md)** <br/> A Typescript function using Node runtime. | node18 | [Serverless Framework] |
5657
| **[Serverless Gateway Python Example](functions/serverless-gateway-python/README.md)** <br/> A Python serverless API using Serverless Gateway. | python310 | [Python API Framework] |
58+
| **[Go and Transactional Email](functions/go-mail/README.md)** <br/> A Go function that send emails using Scaleway SDK. | go121 | [Serverless Framework] |
5759

5860
### 📦 Containers
5961

@@ -67,7 +69,7 @@ Table of Contents:
6769
| **[Terraform NGINX hello world](containers/terraform-nginx-hello-world/README.md)** <br/> A minimal example running the base NGINX image in a serverless container deployed with Terraform. | N/A | [Terraform] |
6870
| **[Triggers with Terraform](containers/terraform-triggers/README.md)** <br/> Configuring two SQS triggers, used to trigger two containers, one public, one private. | N/A | [Terraform] |
6971

70-
### Jobs
72+
### ⚙️ Jobs
7173

7274
| Example | Language | Deployment |
7375
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|------------------------|

functions/go-mail/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
package-lock.json
3+
.serverless/

functions/go-mail/README.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Go send mail Example
2+
3+
<!-- To update after tutorial release, can't release both page so this will remain in comment for now: This page is related to this tutorial https://www.scaleway.com/en/docs/tutorials/?facetFilters=%5B%22categories%3Atransactional-email%22%5D&page=1 -->
4+
5+
## Requirements
6+
7+
This example assumes you are familiar with how serverless functions work. If needed, you can check [Scaleway's official documentation](https://www.scaleway.com/en/docs/serverless/functions/quickstart/).
8+
9+
This example uses the Scaleway Serverless Framework Plugin. Please set up your environment with the requirements stated in the [Scaleway Serverless Framework Plugin](https://github.com/scaleway/serverless-scaleway-functions) before trying out the example.
10+
11+
## Context
12+
13+
This example shows how to send a mail using a Serverless Function via [Scaleway Go SDK](https://github.com/scaleway/scaleway-sdk-go).
14+
15+
## Description
16+
17+
Using Serverless Compute Functions to send mails is a great way to optimise costs, manage automatic scaling and having
18+
advanced logging and metrics per Function.
19+
20+
## Setup
21+
22+
Once your environment is set up, you can test your function locally with:
23+
24+
```sh
25+
npm install
26+
```
27+
28+
The code uses 3 environment variables: `SCW_DEFAULT_ORGANIZATION_ID`, `SCW_ACCESS_KEY`, `SCW_SECRET_KEY`.
29+
You should define them in `serverless.yml` or using and a `.env` file [doc here](https://github.com/scaleway/serverless-scaleway-functions/blob/master/docs/secrets.md)
30+
31+
## Deploy and run
32+
33+
You can deploy your function with:
34+
35+
```console
36+
serverless deploy
37+
```
38+
39+
Then, from the given URL, you can run:
40+
41+
```console
42+
curl -v -X POST https://YOUR_FUNCTION -H "X-Auth-Token: $SCW_AUTH_TOKEN" --data '{"to": "MAILTO", "subject": "from console test", "message": "very very long msg to api"}'
43+
```
44+
45+
After that you can check your logs in the Scaleway Console.

functions/go-mail/go.mod

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module gomail
2+
3+
go 1.21
4+
5+
require (
6+
github.com/go-playground/validator/v10 v10.17.0
7+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22
8+
)
9+
10+
require (
11+
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
12+
github.com/go-playground/locales v0.14.1 // indirect
13+
github.com/go-playground/universal-translator v0.18.1 // indirect
14+
github.com/kr/pretty v0.3.1 // indirect
15+
github.com/leodido/go-urn v1.2.4 // indirect
16+
github.com/rogpeppe/go-internal v1.12.0 // indirect
17+
github.com/stretchr/testify v1.8.4 // indirect
18+
golang.org/x/crypto v0.18.0 // indirect
19+
golang.org/x/net v0.20.0 // indirect
20+
golang.org/x/sys v0.16.0 // indirect
21+
golang.org/x/text v0.14.0 // indirect
22+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
23+
gopkg.in/yaml.v2 v2.4.0 // indirect
24+
)

functions/go-mail/go.sum

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
6+
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
7+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
8+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
9+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
10+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
11+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
12+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
13+
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
14+
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
15+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
16+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
17+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
18+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
19+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
20+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
21+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
22+
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
23+
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
24+
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
25+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
26+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
27+
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
28+
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
29+
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
30+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 h1:wJrcTdddKOI8TFxs8cemnhKP2EmKy3yfUKHj3ZdfzYo=
31+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
32+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
33+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
34+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
35+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
36+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
37+
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
38+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
39+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
40+
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
41+
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
42+
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
43+
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
44+
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
45+
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
46+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
47+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
48+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
49+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
50+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
51+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
52+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
53+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
54+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
55+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

functions/go-mail/handler.go

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package gomail
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"os"
8+
"time"
9+
10+
"github.com/go-playground/validator/v10"
11+
tem "github.com/scaleway/scaleway-sdk-go/api/tem/v1alpha1"
12+
"github.com/scaleway/scaleway-sdk-go/scw"
13+
)
14+
15+
// Body to send to the function (via curl for example or Messaging & Queuing).
16+
17+
// {
18+
// "to": "[email protected]",
19+
// "subject": "from console test",
20+
// "message": "very very long msg"
21+
// }
22+
23+
// Region used to call the API.
24+
const region = scw.RegionFrPar
25+
26+
// Data holds the body of the HTTP call. Fields in Data must be completed
27+
// to send an email
28+
type Data struct {
29+
Subject string `json:"subject" validate:"required"`
30+
Message string `json:"message" validate:"required"`
31+
To string `json:"to" validate:"required,email"`
32+
}
33+
34+
// Handler is the entrypoint of the function.
35+
func Handler(respWriter http.ResponseWriter, req *http.Request) {
36+
// Only allow POST verbs
37+
if req.Method != http.MethodPost {
38+
respWriter.WriteHeader(http.StatusMethodNotAllowed)
39+
40+
return
41+
}
42+
43+
var body Data
44+
45+
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
46+
respWriter.WriteHeader(http.StatusBadRequest)
47+
48+
return
49+
}
50+
51+
// Validate the email data, return a 400 error if not valid
52+
53+
if err := validator.New().Struct(body); err != nil {
54+
respWriter.WriteHeader(http.StatusBadRequest)
55+
_, _ = respWriter.Write([]byte(err.Error()))
56+
57+
return
58+
}
59+
60+
if err := sendMail(body.Subject, body.Message, body.To, "CHANGE_ME", false); err != nil {
61+
respWriter.WriteHeader(http.StatusInternalServerError)
62+
_, _ = respWriter.Write([]byte(err.Error()))
63+
64+
return
65+
}
66+
}
67+
68+
// sendMail sends a mail to "mailTo" using "from" email.
69+
// If checkMailStatus is at true, the function will take more time to run some calls
70+
// to the API in order to get mail status over time.
71+
func sendMail(subject, content, mailTo, from string, checkMailStatus bool) error {
72+
// Create a Scaleway client
73+
client, err := scw.NewClient(
74+
// Get your organization ID at https://console.scaleway.com/organization/settings
75+
scw.WithDefaultOrganizationID(os.Getenv("SCW_DEFAULT_ORGANIZATION_ID")),
76+
// Get your credentials at https://console.scaleway.com/iam/api-keys
77+
scw.WithAuth(os.Getenv("SCW_ACCESS_KEY"), os.Getenv("SCW_SECRET_KEY")),
78+
// Get more about our availability zones at
79+
// https://www.scaleway.com/en/docs/console/my-account/reference-content/products-availability/
80+
scw.WithDefaultRegion(region),
81+
)
82+
if err != nil {
83+
return fmt.Errorf("error creating scaleway client with sdk %w", err)
84+
}
85+
86+
// Create SDK object to manipulate transactional email.
87+
temAPI := tem.NewAPI(client)
88+
89+
// Create email is used to send the email to the destination.
90+
mailResp, err := temAPI.CreateEmail(&tem.CreateEmailRequest{
91+
Subject: subject,
92+
Text: content,
93+
ProjectID: os.Getenv("SCW_DEFAULT_ORGANIZATION_ID"),
94+
From: &tem.CreateEmailRequestAddress{Email: from},
95+
To: []*tem.CreateEmailRequestAddress{{Email: mailTo}},
96+
Region: region,
97+
})
98+
if err != nil {
99+
return fmt.Errorf("error trying to create and send mail %w", err)
100+
}
101+
102+
if checkMailStatus {
103+
// Now we get the email ID in order to get it's status
104+
emailID := mailResp.Emails[0].ID
105+
106+
mail, err := temAPI.GetEmail(&tem.GetEmailRequest{EmailID: emailID})
107+
if err != nil {
108+
return fmt.Errorf("error trying to get mail on first time %w", err)
109+
}
110+
111+
fmt.Println("mail status :", mail.Status.String())
112+
113+
// This sleep is only for educational purposes, it should be removed for industrial applications.
114+
time.Sleep(5 * time.Second)
115+
116+
mail, err = temAPI.GetEmail(&tem.GetEmailRequest{EmailID: emailID})
117+
if err != nil {
118+
return fmt.Errorf("error trying to get mail on second time %w", err)
119+
}
120+
121+
fmt.Println("mail status after 5 seconds :", mail.Status.String())
122+
// expected output:
123+
// mail status : new
124+
// mail status after 5 seconds : sent
125+
}
126+
127+
return nil
128+
}

functions/go-mail/package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "goe2e",
3+
"version": "1.0.0",
4+
"scripts": {
5+
"test": "echo \"Error: no test specified\" && exit 1"
6+
},
7+
"keywords": [],
8+
"author": "",
9+
"license": "ISC",
10+
"dependencies": {},
11+
"devDependencies": {
12+
"serverless-scaleway-functions": "^0.4.9"
13+
},
14+
"description": ""
15+
}

functions/go-mail/serverless.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
service: gomail
2+
configValidationMode: off
3+
provider:
4+
name: scaleway
5+
runtime: go121
6+
7+
plugins:
8+
- serverless-scaleway-functions
9+
10+
package:
11+
patterns:
12+
- '!node_modules/**'
13+
- '!.gitignore'
14+
- '!.git/**'
15+
16+
functions:
17+
gomail:
18+
handler: Handler
19+
privacy: private
20+
21+
# For more details please check https://github.com/scaleway/serverless-scaleway-functions/blob/master/docs/secrets.md
22+
secret:
23+
SCW_DEFAULT_ORGANIZATION_ID: ${env:SCW_DEFAULT_ORGANIZATION_ID}
24+
SCW_ACCESS_KEY: ${env:SCW_ACCESS_KEY}
25+
SCW_SECRET_KEY: ${env:SCW_SECRET_KEY}

0 commit comments

Comments
 (0)