Send email with Go (net/smtp) over SMTP

Go SMTP example using the standard library: smtp.PlainAuth, STARTTLS on 587 via smtp.Dial + StartTLS (or tls.Dial for 465), a properly formatted MIME message, and the go-mail alternative for attachments.

Install

shell
# standard library — or: go get github.com/wneessen/go-mail (attachments, HTML, retries)

Complete example

Go — STARTTLS on 587, credentials from the environment
package main

import (
	"crypto/tls"
	"fmt"
	"net/smtp"
	"os"
	"strings"
)

func main() {
	host := getenv("SMTP_HOST", "smtp.queensmtp.com")
	port := getenv("SMTP_PORT", "587")
	user, pass := os.Getenv("SMTP_USER"), os.Getenv("SMTP_PASS")
	from, to := "hello@yourdomain.com", "customer@example.com"

	msg := strings.Join([]string{
		"From: Your App <" + from + ">",
		"To: " + to,
		"Subject: Your receipt",
		"MIME-Version: 1.0",
		"Content-Type: text/html; charset=UTF-8",
		"",
		"<h1>Thanks for your order</h1><p>#1042 is confirmed.</p>",
	}, "\r\n")

	auth := smtp.PlainAuth("", user, pass, host)
	tlsCfg := &tls.Config{ServerName: host}

	var c *smtp.Client
	var err error
	if port == "465" { // implicit TLS
		conn, e := tls.Dial("tcp", host+":"+port, tlsCfg)
		if e != nil { panic(e) }
		c, err = smtp.NewClient(conn, host)
	} else {           // STARTTLS
		c, err = smtp.Dial(host + ":" + port)
		if err == nil { err = c.StartTLS(tlsCfg) }
	}
	if err != nil { panic(err) }
	defer c.Quit()

	if err = c.Auth(auth); err != nil { panic(err) }
	if err = c.Mail(from); err != nil { panic(err) }
	if err = c.Rcpt(to); err != nil { panic(err) }
	w, err := c.Data()
	if err != nil { panic(err) }
	fmt.Fprint(w, msg)
	w.Close()
	fmt.Println("sent")
}

func getenv(k, d string) string { if v := os.Getenv(k); v != "" { return v }; return d }

What to know

  • smtp.SendMail() also works for 587 (it performs STARTTLS when offered) but gives no control over TLS config or timeouts; the explicit client above does.
  • PlainAuth refuses to send over an unencrypted connection — a good safety net.
  • For attachments and multipart bodies use github.com/wneessen/go-mail or jordan-wright/email rather than hand-building MIME.

Same code, different provider

Only the host, port and credentials change. The rules per provider are on the SMTP settings hub.

ProviderHostPortUsernamePasswordNote
QueenSMTP smtp.queensmtp.com587 (STARTTLS) / 465 (SSL) / 2525SMTP username from the dashboardSMTP password$5/year + $0.10 per 1,000; 100 free/day
Gmail smtp.gmail.com587 / 465you@gmail.comApp Password500/day, From must be the account
Amazon SES email-smtp.<region>.amazonaws.com587 / 465 / 2587SES SMTP usernameSES SMTP passwordsandbox 200/day until approved
Office 365 smtp.office365.com587user@yourdomain.commailbox passwordSMTP AUTH must be enabled; 30/min
SendGrid smtp.sendgrid.net587 / 465 / 2525apikeyAPI keyno free plan since 2025

Credentials for the example above

Create a free account, verify your sending domain (SPF + DKIM shown in the dashboard), add an SMTP credential, and paste it into the code. 100 emails a day free; $5/year plus $0.10 per 1,000 after that. Prefer HTTPS? The same account has a REST email API.

Get SMTP credentials Test your settings first

Other languages and frameworks

Examples tested 2026-08-30. If a library changed its API, tell us.

Go SMTP — questions

PlainAuth requires TLS. Call StartTLS on 587 before Auth, or connect with tls.Dial on 465.