Introduction

Hello Folks, Writing this blog to know how to send an email in golang. I will not cover the Golang background. For this you can prefer https://golang.org/ OR https://en.wikipedia.org/wiki/Go_(programming_language).

Also the primary focus of this blog will be email sending, I ll keep sharing the relevant resources So that you can go more deeper for a specific terminology.

Requirement of email sending needed in almost every language or project. I found sending email is a bit tricky in golang. There are already multiple libraries present on the internet which will help you to send an email.

But after doing some research I found two packages which have good open source contributions, So that shortlisted below packages.

  1. net/smtp (https://golang.org/pkg/net/smtp/) : This is the core package of golang.
  2. go-gomail/gomail (https://github.com/go-gomail/gomail): This is the community driven project.

Both the packages have their use cases and pros & cons. We will check in details.

net/smtp

net/smtp is the official package of golang for sending an email and it implements SMTP (Simple Mail Transfer Protocol) As defined in RFC 5321.

Auth Methods

Almost every SMTP connection needs authentication (excluding a few special cases), For this there are multiple types of Auth Method available like PLAIN, LOGIN, CRAM-MD5, MD5, DIGEST-MD5 etc. You can read more about SMTP Authentication on this wiki.

net/smtp package used to provide 2 Auth methods.

  1. PlainAuth
  2. CRAMMD5Auth

PlainAuth

send.go

package main

import (
	"log"
	"net/smtp"
)

func main() {

	// Setup host information
	host := "smtp.swipemail.in"
	port := "587"

	// Setup headers
	to := []string{"to@example.com"}
	msg := []byte("To: to@example.com\r\n" +
		"Subject: hello Gophers!\r\n" +
		"\r\n" +
		"This is the email body.\r\n")
	from := "sender@mail250.com"

	// Set up authentication information.
	username := "smtp_username"
	password := "smtp_password"

	auth := smtp.PlainAuth("", username, password, host)

	err := smtp.SendMail( host+":"+port, auth, from, to, msg)
	if err != nil {
		log.Fatal(err)
	}
}

It simply implements the PLAIN authentication mechanism as defined in the RFC 4616. Here username and password is being used for authentication.
net/smtp ensures that this method will only send the credentials if your connection is TLS or its connected to localhost.  If you are trying to send over the insecure connection you might get below error:

x509: certificate has expired or is not yet valid

Usually many people get this error while they are trying to connect with Gmail’s SMTP (smtp.gmail.com) but with the wrong port like 465. In this case you need to change the port to 587.

CRAMMD5Auth

send.go

package main

import (
	"log"
	"net/smtp"
)

func main() {

	// Setup host information
	host := "smtp.mimepost.com"
	port := "587"

	// Setup headers
	to := []string{"to@example.com"}
	msg := []byte("To: to@example.com\r\n" +
		"Subject: hello Gophers!\r\n" +
		"\r\n" +
		"This is the email body.\r\n")
	from := "sender@mimepost.com"

	// Set up authentication information.
	username := "smtp_username"
	password := "smtp_password"

	auth := smtp.CRAMMD5Auth(username, password)

	err := smtp.SendMail( host+":"+port, auth, from, to, msg)
	if err != nil {
		log.Fatal(err)
	}
}

CRAM-MD5 provides a higher level of security compared to the PLAIN and LOGIN Auth mechanism and it implements the authentication as defined in RFC 2195.

As far i tested, This method also ensures to connect over the secure connection (Mostly TLS) otherwise this package will give the same error which shown in above PlainAuth method.

What about the LOGIN Auth ?

Officially this package does not have a LOGIN auth mechanism. But during the research I found the below gist which implements this with the small hack in core package only (net/smtp).

Also you can use this mechanism over the insecure connection like without TLS or SSL.

https://gist.github.com/andelf/5118732

import (
	"net/smtp"
	"errors"
)

type loginAuth struct {
  username, password string
}

func LoginAuth(username, password string) smtp.Auth {
	return &loginAuth{username, password}
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	return "LOGIN", []byte{}, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case "Username:":
			return []byte(a.username), nil
		case "Password:":
			return []byte(a.password), nil
		default:
			return nil, errors.New("Unkown fromServer")
		}
	}
	return nil, nil
}


// usage: 
// auth := LoginAuth("loginname", "password")
// err := smtp.SendMail(smtpServer + ":25", auth, fromAddress, toAddresses, []byte(message))
// or	
// client, err := smtp.Dial(smtpServer)
// client.Auth(LoginAuth("loginname", "password"))

Quick Note

  • The smtp package is frozen and is not accepting new features. Some external packages provide more functionality - golang.org
  • Because this package is no more going to evolve, You cannot expect much in the future.
  • Mostly you cannot send emails over non secure connections.
  • For LOGIN Auth you can use the above hack and test with various cases if you really want to use it on productions. Also it will help to send emails over the non secure  connection, Although it is not recommended but some time there are use cases where you no need to use secure connection like In house email routing / Relay. In such a case you can go for the above hack.
  • Composing the message body is a bit messy.

go-gomail/gomail

It is an efficient package to send an email which is driven by community. You can get more info here https://github.com/go-gomail/gomail.

It provides the LOGIN Authentication mechanism. Whenever it requires it automatically uses CRAM-MD5 when it's available. You don’t need to specify explicitly like net/smtp.

Sample Code

send.go

package main

import (
	"crypto/tls"
	"fmt"
	"gopkg.in/gomail.v2"
)

func main() {
	m := gomail.NewMessage()
	m.SetHeader("From", "alex@example.com")
	m.SetHeader("To", "bob@example.com", "cora@example.com")
	m.SetAddressHeader("Cc", "dan@example.com", "Dan")
	m.SetHeader("Subject", "Hello!")
	m.SetBody("text/html", "Hello <b>Bob</b> and <i>Cora</i>!")
	//m.Attach("/home/Alex/lolcat.jpg")

	d := gomail.NewDialer("smtp.mimepost.com", 2525, "smtp_user", "smtp_password")
	d.TLSConfig = &tls.Config{InsecureSkipVerify: true}

	// Send the email to Bob, Cora and Dan.
	if err := d.DialAndSend(m); err != nil {
		panic(err)
	} else {
		fmt.Print("sent")
	}

}

Send Over Insecure Connection

By default it will connect with a secure connection But you can easily send the email on insecure connections (Without TLS and SSL). Just add the below line:

d.TLSConfig = &tls.Config{InsecureSkipVerify: true}

Example of Ways to send emails using go-gomail/gomail

  • Simple send email
  • A daemon that listens to a channel and sends all incoming messages.
  • Efficiently send a customized newsletter to a list of recipients.
  • Send an email using a local SMTP server
  • Send an email using an API or postfix.

You can find all of the above example here:

https://github.com/go-gomail/gomail/blob/master/example_test.go

Quick Note

  • Less messy code while implementing as compared to net/smtp. Easy to understand. Composing the message body is fair easy
  • PLAIN Auth Mechanism is not present.
  • Easy to send over insecure connection

Some Other SMTP Packages

Here are some other efficient SMTP packages which you can explore.

Which package should I use ?

You can choose any library but every package has some pros and cons. You can explore them all and pick which is best for your case.

While picking the package just check if better support, proper documentations, community support etc are available or not.

You can also benchmark the package according to your use case and then decide.

In some cases I am using net/smtp with LOGIN hack for just simply in-house relay . Similarly you can check which is the best fit for your case.