SMTP golang TLS tutorial using go-smtp

Send emails with secure socket layer over SMTP in go using a popular package.

send email go

Sending email over SMTP in go is easy. Go contains its own net/smtp package but it can be cumbersome. Luckily there exists the excellent open source emersion go-smtp package. In this post we will show you how to send email using SMTP commands in go.

Install dependencies

Add the github.com/emersion/go-smtp dependency to your go project. For this example we will also add MailSlurp to create an SMTP inbox for our test. We can find this at github.com/mailslurp/mailslurp-client-go.

import (
	"context"
	"github.com/antihax/optional"
	sasl "github.com/emersion/go-sasl"
	smtp "github.com/emersion/go-smtp"
	mailslurp "github.com/mailslurp/mailslurp-client-go"
	"github.com/stretchr/testify/assert"
	"log"
	"os"
	"strings"
	"testing"
)
//

Notice the assert package - we can use that to test our SMTP client code.

Get MailServer connection details

We need to send email with Go to and through email mailservers. We can do this if we know a mailservers port and host names. With MailSlurp we can generate a new disposable mailbox and use the details associated with it.

var apiKey = os.Getenv("API_KEY")

func getMailSlurpClient(t *testing.T) (*mailslurp.APIClient, context.Context) {
	assert.NotNil(t, apiKey)

	// create a context with your api key
	ctx := context.WithValue(context.Background(), mailslurp.ContextAPIKey, mailslurp.APIKey{Key: apiKey})

	// create mailslurp client
	config := mailslurp.NewConfiguration()
	client := mailslurp.NewAPIClient(config)

	return client, ctx
}
//

Sending secure email over TLS

To send email safely with Go we should use TLS secure socket connections. We can do this like so:

func Test_CanSendEmail_TLS(t *testing.T) {
	// create a context with your api key
	client, ctx := getMailSlurpClient(t)

	// create an inbox using the inbox controller
	opts := &mailslurp.CreateInboxOpts{
		InboxType: optional.NewString("SMTP_INBOX"),
	}

	// create two inboxes for testing
	inbox1, _, _ := client.InboxControllerApi.CreateInbox(ctx, opts)
	smtpAccess, _, _ := client.InboxControllerApi.GetImapSmtpAccess(ctx, &mailslurp.GetImapSmtpAccessOpts{
		InboxId: optional.NewInterface(inbox1.Id),
	})
	inbox2, _, _ := client.InboxControllerApi.CreateInbox(ctx, opts)

	// send email from inbox1 to inbox2
	auth := sasl.NewPlainClient("", smtpAccess.SmtpUsername, smtpAccess.SmtpPassword)

	// Connect to the server, authenticate, set the sender and recipient,
	// and send the email all in one step.
	to := []string{inbox2.EmailAddress}
	msg := strings.NewReader("To: " + inbox2.EmailAddress + "\r\n" +
		"Subject: Hello Gophers!\r\n" +
		"\r\n" +
		"This is the email body.\r\n")
	// not TLS mailslurp uses a different host
	err := smtp.SendMail("mailslurp.mx:587", auth, inbox1.EmailAddress, to, msg)
	if err != nil {
		log.Fatal(err)
		assert.NoError(t, err, "Expect smtp send to work")
	}

	// fetch the email for inbox2
	waitOpts := &mailslurp.WaitForLatestEmailOpts{
		InboxId:    optional.NewInterface(inbox2.Id),
		Timeout:    optional.NewInt64(30000),
		UnreadOnly: optional.NewBool(true),
	}
	email, _, err := client.WaitForControllerApi.WaitForLatestEmail(ctx, waitOpts)
	assert.NoError(t, err)
	assert.Contains(t, *email.Subject, "Hello Gophers")
	assert.Contains(t, *email.Body, "This is the email body")
}

//

How to send when insecure?

If necessary we can also send emails with TLS using an insecure connection:

func Test_CanSendEmail_Insecure(t *testing.T) {
	// create a context with your api key
	client, ctx := getMailSlurpClient(t)

	// create an inbox using the inbox controller
	opts := &mailslurp.CreateInboxOpts{
		InboxType: optional.NewString("SMTP_INBOX"),
	}

	// create two inboxes for testing
	inbox1, _, _ := client.InboxControllerApi.CreateInbox(ctx, opts)
	smtpAccess, _, _ := client.InboxControllerApi.GetImapSmtpAccess(ctx, &mailslurp.GetImapSmtpAccessOpts{
		InboxId: optional.NewInterface(inbox1.Id),
	})
	inbox2, _, _ := client.InboxControllerApi.CreateInbox(ctx, opts)

	// create a plain auth client with smtp access details
	auth := sasl.NewPlainClient("", smtpAccess.SmtpUsername, smtpAccess.SmtpPassword)

	// dial connection to the smtp server
	c, err := smtp.Dial("mx.mailslurp.com:2525")
	assert.NoError(t, err, "Expect client dial")
	defer c.Close()

	// issue hello smtp command
	log.Println("Say hello")
	err = c.Hello("test")
	assert.NoError(t, err, "Expect hello")

	// issue auth smtp command
	log.Println("Set auth")
	err = c.Auth(auth)
	assert.NoError(t, err, "Expect auth")

	// send the email
	log.Println("Send email")
	to := []string{inbox2.EmailAddress}
	msg := strings.NewReader("To: " + inbox2.EmailAddress + "\r\n" +
		"Subject: Hello Insecure Gophers!\r\n" +
		"\r\n" +
		"This is the email body.\r\n")
	err = c.SendMail(inbox1.EmailAddress, to, msg)
	assert.NoError(t, err, "Expect insecure smtp send to work")

	// fetch the email for inbox2
	log.Println("Wait for email to arrive")
	waitOpts := &mailslurp.WaitForLatestEmailOpts{
		InboxId:    optional.NewInterface(inbox2.Id),
		Timeout:    optional.NewInt64(30000),
		UnreadOnly: optional.NewBool(true),
	}
	email, _, err := client.WaitForControllerApi.WaitForLatestEmail(ctx, waitOpts)

	// assert email contents
	log.Println("Email received: " + *email.Subject)
	assert.NoError(t, err)
	assert.Contains(t, *email.Subject, "Hello Insecure Gophers")
	assert.Contains(t, *email.Body, "This is the email body")
}

//