Simple Go Lang web service to send emails π§ΈβοΈ
In this article, we'll be creating a simple Go application that acts as a REST Service.
The application will have one end-point /api/contact
will run on port 8080
.
Let's start.
What is Go π§Έ ?
Go is a simple and efficient programming language created by Google. It is known for its clean syntax, strong concurrency support, and excellent performance.
Learn more about Go: https://go.dev
Preparing the Go environment
I'm using Ubuntu 20 for this tutorial, If you need to install go on another platform you can refer to the official documentation: https://go.dev/dl/
On Ubuntu installing Go is as simple as running
apt update && apt install golang
and then adding the go binaries to the system path by updating my ~/.bashrc
with these two lines.
export GOPATH=$HOME/go
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
To explain, the lines added above are shell commands commonly used in Go development environments to set up the necessary environment variables.
The first line, export GOPATH=$HOME/go
, sets the value of the GOPATH
environment variable to the path $HOME/go
. The GOPATH
is an important variable in Go that specifies the root directory for Go projects and their dependencies.
In this case, $HOME/go
is the path to the go
directory within the user's home directory ($HOME
). This is where Go packages and binaries will be stored.
The second line, export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
, modifies the PATH
environment variable to include the Go binary directories. It appends :/usr/local/go/bin:$GOPATH/bin
to the existing PATH
.
/usr/local/go/bin
is the path where the Go compiler (go
) and other Go tools are typically installed.$GOPATH/bin
is the path where Go binaries (executables) are installed when using thego install
command.
By adding these directories to the PATH
, the system will be able to locate and execute Go-related commands and binaries from anywhere in the shell. This allows for convenient usage of Go tools and running Go programs without specifying their full paths.
Project structure
Our project structure will be like this:
email-handler.go
: Contains the request handler logic for the email service.email-sender.go
: Implements the business logic for sending emails.models.go
: Defines the data models used within the email service.main.go
: Serves as the entry point of the application.go.mod
: Specifies the project's module and its dependencies.email-template.html
: A template for the mail that we'll send.
go-email-service
βββ email-handler.go
βββ email-sender.go
βββ models.go
βββ main.go
βββ email-template.html
βββ config.yaml
βββ go.mod
In go.mod
we'll define the module name and the required Go version for the project.
We will need the yaml
package to read SMTP server configuration from the config file
module main
go 1.20
require gopkg.in/yaml.v2 v2.4.0 // indirect
In the file main.go
we will load SMTP
server configuration from yaml
file and then set up an HTTP server with an endpoint for sending emails and start the server on port 8080
.
config.yaml
smtpHost: example-smtpserver.emailprovider.com
smtpPort: 465
smtpEmail: [email protected]
smtpPassword: example-sender-password
main.go
package main
import (
"log"
"net/http"
"os"
"gopkg.in/yaml.v2"
)
// Global SMTP server configuration variable
var smtpConfig SMTPConfig
// Load SMTP server configuration from the YAML file
func loadSMTPConfig(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return err
}
err = yaml.Unmarshal(data, &smtpConfig)
if err != nil {
return err
}
return nil
}
func main() {
err := loadSMTPConfig("config.yaml")
if err != nil {
log.Fatalf("Failed to load SMTP configuration: %v", err)
}
// Define the endpoint for sending the email
http.HandleFunc("/api/book", SendEmailHandler)
// Start the HTTP server
log.Println("Server listening on http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
For email-hander.go
we can create our handler function SendEmailHandler
receives HTTP requests, parses a JSON request body, composes email messages using HTML template, and sends it concurrently using goroutines
. Finally, it sends a response back to the client indicating successful email delivery. (more about goroutines
here )
package main
import (
"encoding/json"
"fmt"
"bytes"
"html/template"
"log"
"net/http"
"sync"
)
// Handler function for sending email
func SendEmailHandler(w http.ResponseWriter, r *http.Request) {
log.Println("Received request /api/contact")
// Parse the JSON request body
var requestBody RequestBody
err := json.NewDecoder(r.Body).Decode(&requestBody)
if err != nil {
http.Error(w, "Failed to parse request body", http.StatusBadRequest)
return
}
defer r.Body.Close()
// Send the email concurrently
var wg sync.WaitGroup
wg.Add(1)
// Compose the email message
subject := fmt.Sprintf("Contact received for %s", requestBody.Name)
body, err := getEmailBody("email-template.html", requestBody)
if err != nil {
log.Println("Failed to read email template:", err)
http.Error(w, "Failed to read email template", http.StatusInternalServerError)
return
}
go SendEmail(subject, body, requestBody.RecipientEmail, &wg)
// Send a response back to the client
w.WriteHeader(http.StatusOK)
w.Write([]byte("Email sent successfully"))
}
func getEmailBody(templateFile string, data interface{}) (string, error) {
tmpl, err := template.ParseFiles(templateFile)
if err != nil {
return "", err
}
var result bytes.Buffer
err = tmpl.Execute(&result, data)
if err != nil {
return "", err
}
return result.String(), nil
}
The email-sender.go
defines a Go function SendEmail
sends an email using an SMTP server. It establishes a secure connection with the SMTP server, authenticates using the provided credentials, and sends the email message.
package main
import (
"bytes"
"crypto/tls"
"fmt"
"log"
"net/smtp"
"sync"
)
func SendEmail(subject string, body string, to string, wg *sync.WaitGroup) bool {
defer wg.Done()
message := []byte("From: Contact <" + smtpConfig.Sender + ">\r\n" +
"To: " + to + "\r\n" +
"Subject: " + subject + "\r\n" +
"MIME-Version: 1.0\r\n" +
"Content-Type: text/html; charset=utf-8\r\n" +
"\r\n" +
body + "\r\n")
// Create authentication credentials
auth := smtp.PlainAuth("", smtpConfig.Sender, smtpConfig.Password, smtpConfig.SMTPHost)
// Create the TLS configuration
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: smtpConfig.SMTPHost,
}
// Connect to the SMTP server
conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", smtpConfig.SMTPHost, smtpConfig.SMTPPort), tlsConfig)
if err != nil {
log.Printf("Failed to connect to the SMTP server: %v", err)
return false
}
// Create the SMTP client
client, err := smtp.NewClient(conn, smtpConfig.SMTPHost)
if err != nil {
log.Printf("Failed to create SMTP client: %v", err)
return false
}
// Authenticate with the SMTP server
if err := client.Auth(auth); err != nil {
log.Printf("SMTP authentication failed: %v", err)
return false
}
// Set the sender and recipient
if err := client.Mail(smtpConfig.Sender); err != nil {
log.Printf("Failed to set sender: %v", err)
return false
}
if err := client.Rcpt(to); err != nil {
log.Printf("Failed to set recipient: %v", err)
return false
}
// Send the email message
w, err := client.Data()
if err != nil {
log.Printf("Failed to open data writer: %v", err)
return false
}
buf := bytes.NewBuffer(make([]byte, 0, 1024))
buf.Write(message)
_, err = buf.WriteTo(w)
if err != nil {
log.Printf("Failed to write email message: %v", err)
return false
}
err = w.Close()
if err != nil {
log.Printf("Failed to close data writer: %v", err)
return false
}
// Close the connection to the SMTP server
client.Quit()
log.Println("Email sent successfully!")
return true
}
models.go
will contain data structures used in the project like smtp config and http request body
package main
type RequestBody struct {
RecipientEmail string `json:"email"`
PhoneNumber string `json:"phone"`
Name string `json:"name"`
}
type SMTPConfig struct {
SMTPHost string `yaml:"smtpHost"`
SMTPPort int `yaml:"smtpPort"`
Sender string `yaml:"smtpEmail"`
Password string `yaml:"smtpPassword"`
}
Build and run
To build the project, first, we fetch the dependencies (make sure you are in the root folder of the project)
go get .
Then we run
go run .
If you only want to build the project without running it
go build .
An executable file will be generated. On Windows, it will be a main.exe
file (you can double-click it to run) and on Linux or MacOS it will be an executable file main
After running the server and sending a request using curl or Postman

you will get an output like this in the console
2023/05/29 12:30:57 Server listening on http://localhost:8080
2023/05/29 12:31:08 Received request /api/contact
2023/05/29 12:31:10 Email sent successfully!
And you will receive an email with the content from the template
