Send E-Mails with Go using net/smtp package
Working with e-mails with Go is extremely easy, even with a topic so daunting as it.
We’ll leaverage the net/http
and bytes
packages provided by the Go standard library.
Configurations
First of all we need to have the credentials to our smtp server.
Beware that sending emails like this can trigger the spam detection on some providers.
After that some users report your emails as non-spam they’ll not be marked as such.
package mailer
import (
"bytes"
"fmt"
"html/template"
"mime/multipart"
"net/smtp"
"os"
)
var (
from = os.Getenv("SMTP_USER")
pass = os.Getenv("SMTP_PASS")
smtpHost = os.Getenv("SMTP_HOST")
smtpPort = os.Getenv("SMTP_PORT")
alias = os.Getenv("SMTP_ALIAS")
smtpAddr = smtpHost + ":" + smtpPort
auth = smtp.PlainAuth(alias, from, pass, smtpHost)
)
Sending emails with html template as body
Using the html/template
package the email body can be set as a html document
leaveraging the whole power of go templating. Also, the emails will be as pretty as you want 🤗
...
func SendMail(data *internal.Registration, tmpl *html.Template) error {
// initialize the shared buffer
var buf bytes.Buffer
// set the email subject
buf.WriteString("Subject: Confirm your registration\r\n")
// optionally, set the carbon copy
buf.WriteString("Cc: [email protected]\r\n")
// set the mime type as defined in RFC2045
// https://datatracker.ietf.org/doc/html/rfc2045.html
buf.WriteString("MIME-Version: 1.0\n")
buf.WriteString("Content-Type: text/html; charset=utf-8;\n")
// copy the template html output to the shared buffer
tmpl.Execute(&buf, data)
// terminate the message
buf.WriteString("\n\n")
to := []string{data.Email}
// send the resulting buffer with the `net/smtp` package
return smtp.SendMail(smtpAddr, auth, from, to, buf.Bytes())
}
Done! Simply as that. I want to reiterate on what said before. This does not handle complex auth systems and message validation strageties so it can trigger spam protection.
Sending emails with html template as body and attachment
Hold on! How can I handle attachments?
We need to handle the message as a multipart message
(as defined in RFC1314)
As an attachment i will provide a real world example, an ICS calendar.
If you’re interested I’ll covering my ICS library in this article.
...
func SendMail(data *internal.Booking, tmpl *html.Template) error {
var (
buf bytes.Buffer // shared buffer
writer = multipart.NewWriter(&buf) // handles multipart boundaries
boundary = writer.Boundary() // boundary for-each message part
)
// set the email subject
buf.WriteString("Subject: Confirm your registration\r\n")
// optionally, set the carbon copy
buf.WriteString("Cc: [email protected]\r\n")
// set the mime type as defined in RFC2045
// https://datatracker.ietf.org/doc/html/rfc2045.html
buf.WriteString("MIME-Version: 1.0\n")
// set the boundary ref
buf.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=%s\n", boundary))
// set the first boundary
buf.WriteString(fmt.Sprintf("--%s\n", boundary))
// set the main part mime type
buf.WriteString("Content-Type: text/html; charset=utf-8;\n")
// execute the template and write the output to the shared buffer
tmpl.Execute(&buf, data)
// end the first part with the boundary
buf.WriteString(fmt.Sprintf("\n\n--%s\n", boundary))
// generate an ics calendar and write the output to the shared buffer
ics.AsAttachment(&buf, &ics.ICSData{
Id: "PRODID:-//Hello//EN",
Uid: data.Id,
ISODateCreatedAt: ics.FormatDate(time.Now().Format(time.RFC3339)),
ISODateStart: ics.FormatDate(data.Date.Format(time.RFC3339)),
ISODateEnd: ics.FormatDate(data.Date.Add(time.Minute * 30).Format(time.RFC3339)),
Organizer: "Marco",
Summary: "Meeting",
Address: "Elune",
Sender: "mailto:[email protected]",
Email: data.Email,
})
// terminate the second part
buf.WriteString(fmt.Sprintf("\n--%s", boundary))
// terminate the message
buf.WriteString("--")
// send
return smtp.SendMail(smtpAddr, auth, from, []string{data.Email}, buf.Bytes())
}
AsAttachment
essentially does…
...
buf.WriteString("Content-Type: text/calendar; charset=utf-8; method=REQUEST\n")
buf.WriteString("Content-Transfer-Encoding: 7bit\n")
// execute
buf.WriteString("\r\n")
buf.WriteString(data)
buf.WriteString("\r\n")
...
You can add as many attachments by repeating the pattern: boundary, data, newline, –boundary–, newline.
Conclusions
As inherent to the Golang philosophy, even sending emails is super easy and pretty bulletproof.