guides
Go SMTP tutorial with TLS using emersion go-smtp
Send email with Go using the emersion go-smtp package, TLS settings, and a practical test checklist for production SMTP workflows.
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.
If you searched for golang smtp, the practical path is: choose the SMTP library, configure TLS intentionally, send a controlled message, and verify the received result instead of trusting the send call alone.
For the transport background, see SSL for email and SMTP vs HTTP inboxes.
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")
}
//
Use implicit TLS when the server expects encryption from the first byte, and use STARTTLS when the server expects a plaintext SMTP greeting followed by an upgrade. Mixing the mode and port is one of the fastest ways to create confusing Go SMTP failures.
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")
}
//
Go SMTP deployment checklist
Before promoting Go SMTP code into production:
- Confirm the host, port, auth mode, and TLS mode with the SMTP tester.
- Validate SMTP auth and message rendering in Email Sandbox.
- Add CI coverage for signup, reset, and notification paths with Email Integration Testing.
- Capture send outcomes and message lifecycle events using Email Webhooks.
- Route retries, bounces, and suppression decisions through Email Automation Routing.
- Confirm inbox placement and sender reputation with an Email Deliverability Test.