How to Send and Receive Emails with Go

On January 28, 2024
13min read
Piotr Malek Technical Content Writer @ Mailtrap
This is a featured image for an article on how to send emails with Go

Turning 13 this year, Go (a.k.a. Golang) is the fourth most-desired back-end programming language. It was created in Google labs to address the shortcomings of other existing languages. It’s mostly based on C, but it also incorporates the ease of use of Python and JavaScript. 

Whether you’re building an app or your own server in Go, the chances are you’ll need to enable email sending and receiving to connect to your users. There are a few ways to achieve this, with some being more useful than others. 

We’ve put together a simple tutorial on sending emails in Golang with different approaches and code examples. Let’s Go!

How to send emails in Go with the net/smtp package

The easiest way to send emails in Go is to use the SMTP package located within the net package.

SMTP (Simple Mail Transfer Protocol) is used to send emails by moving messages between email clients and mail servers. Golang also relies on this protocol and offers a basic and easy way to add email functionality to a Go app by implementing a library. The smtp package follows RFC 5321 and implements SMTP protocol accordingly. 

The main function of this package is SendMail with the following syntax:

func SendMail(addr string, a Auth, from string, to []string, msg []byte) error

It lets you connect to a server (‘addr’ attribute, which must include a port number — for example, smtp.gmail.com:587). You can then specify the authentication method (more about that below), the sender’s and recipient’s email addresses, and the HTML that will be included in the message. Finally, you should clarify what will happen should an error occur.

To authenticate, you can use either of the two available authentication mechanisms:

  • Plain authentication
  • Cram-MD5 authentication

Plain authentication is the most basic method of authorizing an email client to act on your behalf. You provide your username and password, which are encoded with the basic base64 method. In Golang, this method is represented by the PlainAuth function and comes in the following shape:

func PlainAuth(identity, username, password, host string) Auth 

Cram-MD5 is a little more sophisticated. Using the provided credentials, it utilizes the challenge-response mechanism to authenticate with a server. It adds an extra layer of security and, although it comes with plenty of weaknesses, it’s still a more secure option than PlainAuth. The CRAMMD5Auth function comes with the following syntax:

func CRAMMD5Auth(username, secret string) Auth

The “secret” used in the previous code is either the user’s password or a hash of it.

Here’s what a sample code would look like if you were to send an email with Gmail SMTP and SendMail function: 

package main

import (

"log"

"net/smtp"

)

func main() {

// Choose auth method and set it up

auth := smtp.PlainAuth("", "john.doe@gmail.com", "extremely_secret_pass", "smtp.gmail.com")

// Here we do it all: connect to our server, set up a message and send it

to := []string{"kate.doe@example.com"}

msg := []byte("To: kate.doe@example.com\r\n" +

"Subject: Why aren’t you using Mailtrap yet?\r\n" +

"\r\n" +

"Here’s the space for our great sales pitch\r\n")

err := smtp.SendMail("smtp.gmail.com:587", auth, "john.doe@gmail.com", to, msg)

if err != nil {

log.Fatal(err)

}

}

The code sample above uses PlainAuth to authenticate

Since May 2022, Google doesn’t support less secure app access which was necessary to use the SendMail function with Gmail accounts. The alternative is to enable 2-step verification and use an app password for authentication. You can learn how to create and use app passwords here

Once you generate the app password, you should use it instead of your regular Gmail password whenever required. In the code sample above, it would substitute extremely_secret_pass in the auth section: 

auth := smtp.PlainAuth("", "john.doe@gmail.com", "app_password", "smtp.gmail.com")

Remember: don’t use actual passwords in the code. Even if you’re just playing around or testing the app, it’s reasonable to change these values with environmental variables. You can load them later with os.Getenv.

Several things to note: the SendMail function will automatically use TLS encryption if the server supports it — any major SMTP server will do so. Also, SendMail doesn’t offer support for DKIM authentication or MIME attachments. And one more thing, the net/smtp package doesn’t accept new features as it’s frozen. 

Luckily, there are alternative methods that support authentication protocols and attachments. 

How to send emails with Gomail

To send emails in Go with SMTP you could also use the Gomail package. While net/smtp is Go’s native package, Gomail is community-driven. It addresses the limitations we’ve described above by supporting text/HTML templates, attachments, embedded images, and automatic encoding of special characters. 

package main

import (

"github.com/go-mail/mail"

)

func main() {

m := mail.NewMessage()

m.SetHeader("From", "john.doe@gmail.com")

m.SetHeader("To", "kate.doe@example.com", "noah.doe@example.com")

m.SetAddressHeader("Cc", "oliver.doe@example.com", "Oliver")

m.SetHeader("Subject", "Hello!")

m.SetBody("text/html", "Hello <b>Kate</b> and <i>Noah</i>!")

m.Attach("lolcat.jpg")

d := mail.NewDialer("smtp.gmail.com", 587, "john.doe@gmail.com", "123456")

// Send the email to Kate, Noah and Oliver.

if err := d.DialAndSend(m); err != nil {

panic(err)

}

}

The main functions Gomail uses are NewDialer (initiates a new SMTP connection), NewMessage (creates a new message), and Send (sends the message). 

Gomail also has the function Attach which makes it possible to send emails with attachments (you know, the images of your cat you include with every single email). We’ll talk about attachments in more detail later. Here’s what func Attach looks like:

func (m *Message) Attach(filename string, settings ...FileSetting) 

You’ll just need to specify the filename and its type as in the example below: 

m.Attach("lolcat.jpg")

Unlike net/smtp package, Gomail mail module doesn’t require to specify authentication methods. By default, it uses the LOGIN mechanism and switches to CRAM-MD5 whenever possible. More information on Gomail is available on Github

How to send emails via Golang and third-party email API

To send emails via Golang with a third-party email API, you have two main options: 

  • Use the email API as an SMTP email server; 
  • Integrate it in your Golang app using the API key and token. 

While there’s nothing wrong with using free SMTP servers for sending emails, they have strict sending volume limitations.

In some cases, setting up a local SMTP server is a more viable option, but it can compromise the email sender reputation and reduce email deliverability. For that reason, using third-party email APIs is the best solution for sending high volumes of emails. 

Now let’s see how that’s done with Golang. 

Sending emails with a third-party SMTP server

To send emails with a third-party SMTP server, you can use Mailtrap Email Sending – an email infrastructure with high deliverability rates by design. It provides both an email API and an SMTP services for easy and smooth integration.

The integration process will look the same as with the net/smtp package, but before getting started, you’d need to verify your domain and get SMTP credentials. 

Here’s how: 

  • Create a Mailtrap account and log in; 
  • On the left-side panel, press Email Sending and click on Add Your First Domain
  • Type in your domain name, press Add, and follow the verification steps;
  • Once the domain is verified, navigate to Sending Domains;
  • Choose between the Transactional and Bulk stream depending on the type of emails you want to send;
  • Press SMTP, copy your credentials, and paste them into your Go app. 
This is an image showing the Mailtrap Email Sending transactional and bulk email-sending screen

If everything is done correctly, your code should look like the one below with your credentials in it: 

package main

import (

"log"

"net/smtp"

)

func main() {

// Mailtrap account config

username := "api"

password := "<secret_token>"

smtpHost := "live.smtp.mailtrap.io"

// Choose auth method and set it up

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

// Message data

from := "john.doe@your.domain"

to := []string{"kate.doe@example.com"}

message := []byte("To: kate.doe@example.com\r\n" +

"From: john.doe@your.domain\r\n" +

"\r\n"+

"Subject: Why aren't you using Mailtrap yet?\r\n" +

"\r\n"+

"Here's the space for your great sales pitch\r\n")

// Connect to the server and send message

smtpUrl := smtpHost + ":25"

err := smtp.SendMail(smtpUrl, auth, from, to, message)

if err != nil {

log.Fatal(err)

}

}

At this point, you’ll be able to run the code and send a test email. If the attempt is successful, verify your setup in your Mailtrap account. 

This is an image showing the Verify Your Setup page in Mailtrap Email Sending

You should be able to see your test email in Email Logs within minutes. This means that you can now use Mailtrap Email Sending to send emails from your Go app or project.

Sending emails with an email API through API integration.

If using SMTP to send emails via Golang isn’t an option for you, you can always use the Email API service provided by Mailtrap Email Sending. The process is similar to the SMTP integration we described above, but this time, you have to pick the API in the “SMTP/API Settings” tab after picking either the Transactional or Bulk email-sending stream. Choose Go from the dropdown menu, and you’ll immediately see a sample code. 

This is an image showing the Mailtrap Email Sending Email API integration code in Go

Once you modify the values of “from”, “to”, “subject”, “headers”, “content”, and fill in the empty strings, the code will look like this:

package main

import (

"bytes"

"fmt"

"io"

"log"

"net/http"

"time"

)

func main() {

// Mailtrap account config

token := "<secret_token>"

httpHost := "https://send.api.mailtrap.io/api/send"

// Message body

message := []byte(`{"from":{"email":"john.doe@your.domain"},

"to":[{"email":"kate.doe@example.com"}],

"subject":"Why aren’t you using Mailtrap yet?",

"text":"Here’s the space for your great sales pitch"}`)

// Set up request

request, err := http.NewRequest(http.MethodPost, httpHost, bytes.NewBuffer(message))

if err != nil {

log.Fatal(err)

}

// Set required headers

request.Header.Set("Content-Type", "application/json")

request.Header.Set("Authorization", "Bearer "+token)

// Send request

client := http.Client{Timeout: 5 * time.Second}

res, err := client.Do(request)

if err != nil {

log.Fatal(err)

}

defer res.Body.Close()

body, err := io.ReadAll(res.Body)

if err != nil {

log.Fatal(err)

}

fmt.Println(string(body))

}

Run the code to check if you did everything correctly. If so, you should verify the setup to start sending emails from Golang. After that, you’ll also be able to use other features of the Email Sending, such as tracking, actionable analytics, and up to 60 days of email logs. 

Important note: For the method to work, you’ll need to provide To, From, and Subject parameters as part of the message. It applies to both the API and SMTP methods.

This is an image showing the Mailtrap Stats Overview page in Mailtrap Email Sending

How to send HTML emails in Go

To send engaging and responsive HTML emails in Go, there are two main options: 

  • Use HTML as the body of the message;
  • Use Gomail package;

The first option is based on the idea that most SMTP clients can format HTML themselves. All we have to do is let the recipient’s SMTP client know that it’s receiving an HTML email. To do so, we have to update the Content-Type header and send HTML as the message body:

headers := map[string]string{

"From": "john.doe@gmail.com",

"To": "kate.doe@example.com",

"Subject": "Why aren’t you using Mailtrap yet?",

"Content-Type": "text/html; charset=utf-8",

}

If the Content-Type header is omitted, all the HTML tags will render as plain text. 

Alternatively, you can just use the Gomail package for HTML emails. In the code sample we provided above, the relevant section looks like this: 

m.SetBody("text/html", "Hello <b>Kate</b> and <i>Noah</i>!") 

Gomail also allows you to attach a plain text version to your HTML message for clients who don’t support HTML. This can be done using the AddAlternative function: 

func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting)

Remember, that plain text should be added before the HTML:

m.SetBody( "text/html", "<p>Hello!</p>")

m.AddAlternative("text/plain", "Hello!")

HTML emails can be sent with any Go library that supports HTML

The great thing about Go is that it supports HTML templates with its html/template package. This means that you can build responsive HTMLs to send to your users. There are several tutorials on building templates in Go; in our opinion, this one seems really interesting.

How to send email with attachments using Golang

Apart from the function Attach in Gomail, it’s possible to send email with attachments in Go with the mime/multipart method. MIME is the standard for sending various types of data (files, images, videos, etc.) via email. It encapsulates the body of the message and all the attachments in the multipart message. MIME uses base64 encoding to convert binary text into ASCII type text. 

Sending email with attachments with the mime/multipart method is far from straightforward. It requires the specification of multipart type, body part, and content disposition. That’s why devs prefer to use 3rd-party libraries that simplify that process. Jordan Wright’s “email” library is particularly useful. 

How to send emails to multiple recipients in Go

Sending an email to a single recipient is fairly simple, but what if we have several people in mind? Luckily, this is also really easy to set up. Let’s cut a piece from the net/smtp code sample and focus solely on the message headers and email body:

to := []string{"kate.doe@example.com"}

msg := []byte("To: kate.doe@example.com\r\n" +

"Subject: Why aren’t you using Mailtrap yet?\r\n" +

"\r\n" +

"Here’s the space for our great sales pitch\r\n")

Notice how the recipient’s email address is placed in both the headers and the body. To send emails to more than one recipient, we’ll need to add them to both parts, separated by a comma.

to := []string{"kate.doe@example.com", “noah.doe@example.com}

msg := []byte("To: kate.doe@example.com, noah.doe@example.com \r\n" +

"Subject: Why you’re not using Mailtrap yet?\r\n" +

"\r\n" +

"Here’s the space for our great sales pitch\r\n")

Now, let’s assume this email is mainly addressed to Kate Doe, but we want Noah Doe to be cc’ed.

to := []string{"kate.doe@example.com"}

cc:= []string{“noah.doe@example.com”}

msg := []byte("To: kate.doe@example.com\r\n" +

“Cc: noah.doe@example.com\r\n"

"Subject: Why you’re not using Mailtrap yet?\r\n" +

"\r\n" +

"Here’s the space for our great sales pitch\r\n")

Finally, let’s imagine we don’t want Kate to know that Noah knows. This is done by adding Noah as bcc to the function parameters but excluding him from the message headers.

to := []string{"kate.doe@example.com", “noah.doe@example.com}

msg := []byte("To: kate.doe@example.com\r\n" +

"Subject: Why aren’t you using Mailtrap yet?\r\n" +

"\r\n" +

"Here’s the space for our great sales pitch\r\n")

All the instructions mentioned thus far in the article are also covered in our dedicated Mailtrap tutorial:

Testing emails before sending in Go: why and how? 

Now you know the different approaches to sending emails with Golang. You might be tempted to skip the testing part and start sending messages right away. But before you do that, you should test your emails to ensure they don’t end up in the spam folder or get discarded. You should also check HTML/CSS if you’re using complex HTML templates. 

Mailtrap Email Testing is an email sandbox that traps all the SMTP traffic from staging. It provides spam analysis and HTML check, among other features.

Try Mailtrap for Free

The spam analysis feature checks your emails’ spam score and sender IP/domain blacklist presence. It uses the Apache SpamAssassin filter to determine the spam score of your messages. Each component of the spam analysis is displayed next to a color-coded score chart. 

If the spam score is above 5 points, you’ll easily find the culprit and correct the mistakes on the go. Email Testing will also check your domains against the most common blacklists and display the results in the Blacklists Report tab. 

This is an image showing the Mailtrap Email Testing Spam Analysis feature

Apart from spam analysis, Mailtrap Email Testing also provides a comprehensive HTML check. It examines your email to find problematic elements. Then, it displays results based on the email client support. You can press the numbers alongside unsupported clients to open the Notes tab with detailed explanations and view problematic lines of code in the HTML Source window. 

This is an image showing the Mailtrap Email Testing HTML Check feature

With Email Testing, you can easily manage your testing process and share it with other team members, create a new project, and add multiple inboxes within it. 

This is an image showing the Mailtrap Email Testing Projects page

To start testing emails in Go, navigate to the Email Testing tab, select an inbox, press SMTP settings, and open Show Credentials. You’ll immediately see the credentials of Mailtrap’s fake SMTP server.

This is an image showing the Mailtrap Email Testing SMTP and POP settings

Copy the credentials and integrate them into the Go app. The code will be similar to the one we described while talking about using Mailtrap Email Sending’s SMTP service. The only difference would be in the func main () { section: 

// Mailtrap account config

username := "<inbox_username>"

password := "<secret_password>"

smtpHost := "sandbox.smtp.mailtrap.io"

Once you run the code, you’ll be able to start sending test emails from Golang to the virtual inbox of the Mailtrap Email Testing. 

How to receive emails in Golang?

Just as with sending, receiving emails is also quite simple in Golang. You can use simple go-pop3 and go-imap packages or leverage the external packages created by the members of the community. 

To build a POP3 client, first you’ll need to install the go-pop3 package with the go get command:

go get -u github.com/knadh/go-pop3

Once the package is installed, building the POP3 client is an easy task. For that, you’ll need to specify the host and the port. Then create a new connection with p.NewConn(). Remember that POP3 connections are stateful, meaning that they remember the client data. So, you should always close the connection with Quit() once you’re done. 

Go-pop3 package allows you to read one (func (*Conn) ReadOne) or all (func (*Conn) ReadAll) lines of connection and pull all messages from the server (fmt.Println). The messages can be deleted from the server only after the successful Quit ()

package main

import (

"fmt"

"log"

"github.com/knadh/go-pop3"

)

func main() {

// Initialize the client.

p := pop3.New(pop3.Opt{

Host: "pop.gmail.com",

Port: 995,

TLSEnabled: true,

})

// Create a new connection. POP3 connections are stateful and should end

// with a Quit() once the operations are done.

c, err := p.NewConn()

if err != nil {

log.Fatal(err)

}

defer c.Quit()

// Authenticate.

if err := c.Auth("myuser", "mypassword"); err != nil {

log.Fatal(err)

}

// Print the total number of messages and their size.

count, size, _ := c.Stat()

fmt.Println("total messages=", count, "size=", size)

// Pull the list of all message IDs and their sizes.

msgs, _ := c.List(0)

for _, m := range msgs {

fmt.Println("id=", m.ID, "size=", m.Size)

}

// Pull all messages on the server. Message IDs go from 1 to N.

for id := 1; id <= count; id++ {

m, _ := c.Retr(id)

fmt.Println(id, "=", m.Header.Get("subject"))

// To read the multipart e-mail bodies, see:

// https://github.com/emersion/go-message/blob/master/example_test.go#L12

}

// Delete all the messages. Server only executes deletions after a successful Quit()

for id := 1; id <= count; id++ {

c.Dele(id)

}

}

Like POP3, you’ll need to download the go-imap package to build the IMAP client.

So, first, install go-imap with this command:

go get github.com/emersion/go-imap

You might also need to run this command:

go get github.com/emersion/go-imap/client@v1.2.1

Once that’s done, you’ll need to start a new connection and specify the number of messages you want to receive. You can access all mailboxes with the Println function and select a specific inbox with c.Select. You can also choose the number of messages you want to access and close the connection with log.Println("Done!").

package main

import (

"log"

"github.com/emersion/go-imap/client"

"github.com/emersion/go-imap"

)

func main() {

log.Println("Connecting to server...")

// Connect to server

c, err := client.DialTLS("mail.example.org:993", nil)

if err != nil {

log.Fatal(err)

}

log.Println("Connected")

// Don't forget to logout

defer c.Logout()

// Login

if err := c.Login("username", "password"); err != nil {

log.Fatal(err)

}

log.Println("Logged in")

// List mailboxes

mailboxes := make(chan *imap.MailboxInfo, 10)

done := make(chan error, 1)

go func () {

done <- c.List("", "*", mailboxes)

}()

log.Println("Mailboxes:")

for m := range mailboxes {

log.Println("* " + m.Name)

}

if err := <-done; err != nil {

log.Fatal(err)

}

// Select INBOX

mbox, err := c.Select("INBOX", false)

if err != nil {

log.Fatal(err)

}

log.Println("Flags for INBOX:", mbox.Flags)

// Get the last 4 messages

from := uint32(1)

to := mbox.Messages

if mbox.Messages > 3 {

// We're using unsigned integers here, only subtract if the result is > 0

from = mbox.Messages - 3

}

seqset := new(imap.SeqSet)

seqset.AddRange(from, to)

messages := make(chan *imap.Message, 10)

done = make(chan error, 1)

go func() {

done <- c.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)

}()

log.Println("Last 4 messages:")

for msg := range messages {

log.Println("* " + msg.Envelope.Subject)

}

if err := <-done; err != nil {

log.Fatal(err)

}

log.Println("Done!")

}

Wrapping up

This wraps up our guide on sending emails with Golang. As you can see, the choice comes down to either utilizing the native packages and functionalities of Go or connecting to 3rd party libraries or SMTP servers such the one provided by Mailtrap Email Sending

Remember to test your emails before sending them to your users. With Mailtrap Email Testing, devs automate testing workflow, capture SMTP traffic from staging, and do HTML/CSS checks. 

If you’re interested in sending and receiving emails in other frameworks, check out our blog posts about C#, ASP.NET C#, ASP.NET Core, Python, PHP, or Ruby on Rails.

Article by Piotr Malek Technical Content Writer @ Mailtrap