- read

How to Create an Email Alert System in Golang

Shubham Chadokar 160

Photo by Markus Spiske on Unsplash

In this tutorial, we will create a simple email alert system in Golang. We will mock a weather API as an IoT Device and an email service to send the email.

Consider a scenario where a warehouse is storing items that will spoil if the temperature rises above 30°C. To maintain the temperature, you installed temperature sensors in the warehouse. You periodically read the temperature using sensors, check the reading, and send an alert email if the temperature crosses the threshold of 28°C.

At the end of the article, you can find a list of different use cases where you can create an email alert system.

To mock the IoT device readings, you will use a weather API to get the temperature.

The system will work as follows:

  1. The IoT device will periodically poll the weather API to get the current weather conditions.
  2. If the weather conditions are outside of a predefined threshold, the device will send an alert email to the user.
  3. The Email service will be used to send the email.

We will use the following technologies in this project:

  • Golang: Golang is a programming language that is known for its efficiency and simplicity. Install Golang. This tutorial is tested on go version 1.19.
  • Weather API: We will use the Weatherstack API to get the current weather conditions. Create a free account on Weatherstack. They provide 1,000 free requests per month.
  • Email Service: For an email service provider, we will use Brevo(Sendinblue). Create a free account on Brevo. Once your account is created, generate a new API Key from the SMTP and API section.

I hope your setup is complete. Now open any of your favorite editors, like VS Code.

Setup

Create a new project goemailalert.

Open the terminal in the project and initialize the Go modules.

go mod init goemailalert

Install dependencies

go get github.com/getbrevo/brevo-go

Directory structure

To keep the tutorial simple and clean, we will use a basic directory structure.

goemailalert
|-dto
|-response.go
|-mailtemplate
|-alert.go
|-main.go

Create a file response.go under dto . The response.go save the structure of the response of weatherstack API.

// goemailtemplate/dto/response.go
package dto

type WeatherAPIResponse struct {
Location Location `json:"location"`
Current Current `json:"current"`
}

type Location struct {
Name string `json:"name"`
}

type Current struct {
Temperature int `json:"temperature"`
}

The weather API returns a lot of data, but we are going to use only Location and Temperature.

Create a file alert.go under mailtemplate. This html template we will use to send the email.

// goemailalert/mailtemplate/alert.go
package mailtemplate

const (
AlertTemplate = `<html>
<head>
<title>Threshold Cross Alert</title>
</head>
<body>
<p>This is to inform you that threshold value of the device is crossed. Please take necessary actions.
</p>
Threshold Value: %d </br>
Current Value: %d
</body>
</html>`
)

Create the entry point of the application main.go.

Before you start, make sure to keep Weatherstack and Brevo API Key handy.

Following are the constants we will use. Update the constants

// goemailalert/main.go

package main

const (
// Your valid email id or brevo account email id
fromEmail = "[email protected]"
// Your name or alias
fromName = "Jon"
// Valid email address
toEmail = "[email protected]"
// Name of the recipient or email address
toName = "alice"
subject = "Weather Alert!"
// threshold temperature in Celcius
thresholdValue = 28
// Valid City
city = "New Delhi"

// Email service api key
EmailAPIKey = "xxxxxxx-xxxxxxxxxxxxxxxxxx-xxxxxxxxxxx"
// Weather API key
WeatherAPIKey = "xxxxxxxxxxxxxxxxxxxxxxxx"
)

Mock the IoT device

Get the temperature of a city.

func getCityTemperature(city string) (int, error) {
url := fmt.Sprintf("http://api.weatherstack.com/current?access_key=%s&query=%s", WeatherAPIKey, city)

method := "GET"
client := &http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
return 0, err
}

res, err := client.Do(req)
if err != nil {
return 0, err
}
defer res.Body.Close()

body, err := io.ReadAll(res.Body)
if err != nil {
return 0, err
}

v := &dto.WeatherAPIResponse{}

err = json.Unmarshal(body, v)
if err != nil {
log.Println("error while unmarshalling weather api response", err)
return 0, err
}

fmt.Println(string(body), v.Current.Temperature)
return v.Current.Temperature, nil
}

Email service

When you use any SDK, you first have to configure it. Following are the steps you have to follow to send an email:

  • Use NewConfiguration to configure the API Key
  • Create a new API Client using the configuration
  • Create a new email using SendSmtpEmail
  • Send the email using TransactionalEmailsApi.SendTransacEmail
import (
brevo "github.com/getbrevo/brevo-go/lib"
)

func sendEmail(toEmail, toName string, data int) error {
var ctx context.Context
cfg := brevo.NewConfiguration()
//Configure API key authorization: api-key
cfg.AddDefaultHeader("api-key", EmailAPIKey)

// Create New API Client using the configuration
br := brevo.NewAPIClient(cfg)

// Email metadata
meta := brevo.SendSmtpEmail{
To: []brevo.SendSmtpEmailTo{
{Email: toEmail, Name: toName},
},
Subject: subject,
HtmlContent: fmt.Sprintf(mailtemplate.AlertTemplate, thresholdValue, data),
Sender: &brevo.SendSmtpEmailSender{
Name: fromName, Email: fromEmail,
},
}

// send a transactional email
cr, res, err := br.TransactionalEmailsApi.SendTransacEmail(ctx, meta)

if err != nil {
return err
}

log.Println(res.Status, cr.MessageId)
return nil
}

main

To keep the alert system active, you will run the validation in an infinite for loop with a finite interval of 30 seconds. You can reduce the interval for testing, for ex. 10 seconds.

func main() {
fmt.Println("alerting system is online....")

for {
time.Sleep(30 * time.Second)

// get city temperature
temperature, err := getCityTemperature(city)
if err != nil {
log.Println("error while fetching city temperature", err)
continue
}

// check the condition
if temperature > thresholdValue {
log.Println("threshold of city temperature is crossed. sending an alert email...",
"threshold temp:", thresholdValue,
"temperature", temperature)

// send email
err := sendEmail(toEmail, toName, (temperature))
if err != nil {
log.Println("error while sending alert email", err)

continue
}

log.Println("alert email successfully sent.")
}
}
}

Complete Code

// goemailalert/main.go
package main

import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"

"goemailalert/dto"
"goemailalert/mailtemplate"

brevo "github.com/getbrevo/brevo-go/lib"
)

const (
// Your valid email id or brevo account email id
fromEmail = "[email protected]"
// Your name or alias
fromName = "Jon"
// Valid email address
toEmail = "[email protected]"
// Name of the recipient or email address
toName = "alice"
subject = "Weather Alert!"
// threshold temperature in Celcius
thresholdValue = 28
// Valid City
city = "New Delhi"

// Email service api key
EmailAPIKey = "xxxxxxx-xxxxxxxxxxxxxxxxxx-xxxxxxxxxxx"
// Weather API key
WeatherAPIKey = "xxxxxxxxxxxxxxxxxxxxxxxx"
)

func main() {
fmt.Println("alerting system is online....")

for {
time.Sleep(10 * time.Second)

// get city temperature
temperature, err := getCityTemperature(city)
if err != nil {
log.Println("error while fetching city temperature", err)
continue
}

// check the condition
if temperature > thresholdValue {
log.Println("threshold of city temperature is crossed. sending an alert email...",
"threshold temp:", thresholdValue,
"temperature", temperature)

// send email
err := sendEmail(toEmail, toName, (temperature))
if err != nil {
log.Println("error while sending alert email", err)

continue
}

log.Println("alert email successfully sent.")
}
}
}

func getCityTemperature(city string) (int, error) {
url := fmt.Sprintf("http://api.weatherstack.com/current?access_key=%s&query=%s", WeatherAPIKey, city)

method := "GET"
client := &http.Client{}
req, err := http.NewRequest(method, url, nil)
if err != nil {
return 0, err
}

res, err := client.Do(req)
if err != nil {
return 0, err
}
defer res.Body.Close()

body, err := io.ReadAll(res.Body)
if err != nil {
return 0, err
}

v := &dto.WeatherAPIResponse{}

err = json.Unmarshal(body, v)
if err != nil {
log.Println("error while unmarshalling weather api response", err)
return 0, err
}

fmt.Println(string(body), v.Current.Temperature)
return v.Current.Temperature, nil
}

func sendEmail(toEmail, toName string, data int) error {
var ctx context.Context
cfg := brevo.NewConfiguration()
//Configure API key authorization: api-key
cfg.AddDefaultHeader("api-key", EmailAPIKey)

// Create New API Client using the configuration
br := brevo.NewAPIClient(cfg)

// Email metadata
meta := brevo.SendSmtpEmail{
To: []brevo.SendSmtpEmailTo{
{Email: toEmail, Name: toName},
},
Subject: subject,
HtmlContent: fmt.Sprintf(mailtemplate.AlertTemplate, thresholdValue, data),
Sender: &brevo.SendSmtpEmailSender{
Name: fromName, Email: fromEmail,
},
}

// send a transactional email
cr, res, err := br.TransactionalEmailsApi.SendTransacEmail(ctx, meta)

if err != nil {
return err
}

log.Println(res.Status, cr.MessageId)
return nil
}

Run the Alert System

Open your terminal and run the following command. Change the threshold temperature for testing if it doesn’t trigger.

go run main.go

Output

$ go run main.go 
alerting system is online....
{"request":{"type":"City","query":"New Delhi, India","language":"en","unit":"m"},"location":{"name":"New Delhi","country":"India","region":"Delhi","lat":"28.600","lon":"77.200","timezone_id":"Asia\/Kolkata","localtime":"2023-08-20 00:11","localtime_epoch":1692490260,"utc_offset":"5.50"},"current":{"observation_time":"06:41 PM","temperature":30,"weather_code":143,"weather_icons":["https:\/\/cdn.worldweatheronline.com\/images\/wsymbols01_png_64\/wsymbol_0006_mist.png"],"weather_descriptions":["Mist"],"wind_speed":4,"wind_degree":10,"wind_dir":"N","pressure":1005,"precip":0,"humidity":89,"cloudcover":50,"feelslike":33,"uv_index":1,"visibility":3,"is_day":"no"}} 30
2023/08/20 00:14:40 threshold of city temperature is crossed. sending an alert email... threshold temp:
28 temperature 30
2023/08/20 00:14:41 201 Created <[email protected]>
2023/08/20 00:14:41 alert email successfully sent.

Congratulations! You created your own email alerting system.

Next Steps

  • You can add environment variables to keep your API keys safe.
  • Add a cooldown period after each successful email. Sleep for 10 minutes after the first email, then 30 minutes after the second email. In this way, you can save some free email.
  • You can create other email alerts on the Stock market, Sporting Events, Personal reminders, and E-Commerce price drop alerts.

GitHub Link — https://github.com/schadokar/blog-projects

Thanks for reading.