Sending E-mails with Go.
Tags: smtp, go, api, concurrency
Published: 2022-08-21 22:05:57

Sending email notifications are essential for effective communication between users and service providers. Beneath this notable piece of technology lies several layers of abstractions which might require more than a blog post to have a clear or complete idea of; which in retrospect isn't the aim of this post. 
In this short article i will quickly try to uncover some issues i faced whilst trying to implement the email notifications for a service i was building at work.
By default using the standard [SMTP](https://www.techtarget.com/whatis/definition/SMTP-Simple-Mail-Transfer-Protocol) package provided by golang i.e `net/smtp` suffices for most use cases, but depending on the [Email service provider](https://www.activecampaign.com/glossary/email-service-provider) you're using, you might experience some bottle necks using the `smtp` package. 
We use **Webmail** where i work and the first time i tried using `net/smtp` with it, i experienced some irregularities, for instance my client was successfully able to authenticate my credentials, and send the mail message using the `smtp.SendMail` method, with the sample code below:
```
package mails
import (
         "log"
         "net/smtp"
)

var (
	From_mail     = os.Getenv("FROM_MAIL")
	Mail_password = os.Getenv("MAIL_PASSWORD")
	SMTP_Host     = os.Getenv("HOST")
)
func SendMail(msg string, recipient []string) {
	// Message.
	message := []byte("This is a test email message.")

	// Authentication.
	auth := smtp.PlainAuth("", From_mail, Mail_password, SMTP_Host)

	fmt.Println(auth)
	// Sending email.
	if err := smtp.SendMail(fmt.Sprintf("%s:%d", SMTP_Host, 587), auth, From_mail, recipient, message); err != nil {
		log.Printf("Error sending mail %v", err)
		return
	}

	log.Println("Email Sent Successfully!")
}
```
but on the recieving end no mails were delivered.

After series of relentless searching for a reasonable explanations to why i was experiencing such, i got to learn some email service providers like the one i used preferred sending mails over port `465` requiring an ssl connection from the very beginning (without starttls), as compared to the standard `587` which uses plain **TCP** to send the mail traffic to the server with subsequent calls using **Starttls**. 
There's been argument over these ports for a long time, but generally sending mails over `port 587` is more recommended as it is provides a much secured transmission layer compared to port `465`. How these protocols are implemented depends mainly on the service providers you're using and the network protocol they choose for each port, for example Gmail uses SSL for the SMTP server on port 465 and TLS for port 587, how **SSL** and **TLS** are implemented and used for secure data tranmission on the internet is beyond the scope of this article, but you can read more about these protocols [here](https://www.hostinger.com/tutorials/what-is-ssl-tls-https).

Luckily i found this github ["gist"](https://gist.github.com/chrisgillis/10888032), which solved the problem we've been trying to figure out. I refactored the code to fit my use case, but you can get the actual solution from the "gist" link above, now unto the actual code: 

```
package main

import (
	"crypto/tls"
	"fmt"
	"log"
	"net/mail"
	"net/smtp"
	"os"
	"sync"
)

var (
	From_mail     = os.Getenv("FROM_MAIL")
	Mail_password = os.Getenv("MAIL_PASSWORD")
	SMTP_Host     = os.Getenv("HOST")
	Mail_subject  string
	Mail_body     string

	from      *mail.Address
	auth      smtp.Auth
	tlsconfig *tls.Config
	mailwg    sync.WaitGroup
)

type Container struct {
	m       sync.Mutex
	Headers map[string]string
}

func NewContainer() *Container {
	return &Container{
		Headers: make(map[string]string),
	}
}

func main() {

	SendMails("subject", "article message", []string{"testuser1@gmail.com", "testuser2@gmail.com"})
}

func init() {
	from = &mail.Address{Name: "Test-mail", Address: From_mail}
	auth = smtp.PlainAuth("", From_mail, Mail_password, SMTP_Host)
	tlsconfig = &tls.Config{
		InsecureSkipVerify: true,
		ServerName:         SMTP_Host,
	}
}

func SendSSLMail(subject, msg string, recipient string) {
	to := mail.Address{Name: "", Address: recipient}

	Mail_subject = subject
	Mail_body = msg

	// initialize new container object
	container := NewContainer()
	// call mutex.lock to avoid multiple writes to
	// one header instance from running goroutines
	container.m.Lock()
	container.Headers["From"] = from.String()
	container.Headers["To"] = to.String()
	container.Headers["Subject"] = Mail_subject
	// unlock mutex after function returns
	defer container.m.Unlock()

	// Setup message
	message := ""
	for k, v := range container.Headers {
		message += fmt.Sprintf("%s: %s\r\n", k, v)
	}
	message += "\r\n" + Mail_body

	conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", SMTP_Host, 465), tlsconfig)
	if err != nil {
		log.Printf("Error sending mail %v", err)
		return
	}

	c, err := smtp.NewClient(conn, SMTP_Host)
	if err != nil {
		log.Printf("Error sending mail %v", err)
		return
	}

	// Auth
	if err = c.Auth(auth); err != nil {
		log.Printf("Error sending mail %v", err)
		return
	}

	// To && From
	if err = c.Mail(from.Address); err != nil {
		log.Printf("Error sending mail %v", err)
		return
	}

	if err = c.Rcpt(to.Address); err != nil {
		log.Printf("Error sending mail %v", err)
		return
	}

	// Data
	w, err := c.Data()
	if err != nil {
		log.Printf("Error sending mail %v", err)
		return
	}

	_, err = w.Write([]byte(message))
	if err != nil {
		log.Printf("Error sending mail %v", err)
		return
	}

	err = w.Close()
	if err != nil {
		log.Printf("Error sending mail %v", err)
		return
	}

	if err = c.Quit(); err != nil {
		return
	}
}

// Concurrently sending mails to multiple recipients
func SendMails(subject, msg string, recipients []string) {
	mailwg.Add(len(recipients))
	for _, v := range recipients {
		go func(recipient string) {
			defer mailwg.Done()
			SendSSLMail(subject, msg, recipient)
		}(v)
	}
	mailwg.Wait()
}
```
In the code above we declared variables to hold our smtp credentials, then we declared a struct named `Container` which basically contains a mutex `m` field, that essentially allows us to avoid [`Race Conditions`](https://medium.com/trendyol-tech/race-conditions-in-golang-511314c0b85) i.e when different threads/goroutines try to access and mutate the state of a resource at the same time, this is to lock the `Headers` field which is the actual email header containing meta-data of each email to be sent. 
The `init()` function allows us to initialize some of the resource to be used before the function is called, you can see we initialize the `auth` variable with our mail credentials, which returns an `smtp.Auth` type; we then move further to set up `tlsconfig` which basically determines how the client and server should handle data transfer.
The `SendMails` function lets us send mails concurrently to avoid calling the `SendSSLMail` function for each number of recipients we want to distribute the mail to, I believe the `SendSSLMail` function body is quite straight-forward and doesn't require much explanation, as at the time of publishing this article, this service is being used so i'm pretty sure it is stable enough to be used by anyone.

## Conclusion
Sending mails are very essential to the growth of any business, as they allow direct communication with the customers. Knowing how to effectively build stable email services is quitessential to every software developer out there. I hope this article helps solve any challenge you might encounter while writing mail services with Go.

Let me know what you think about this article and possibly any amendments that could be made to improve it's user experience, thanks for reading and have a great time!