A step-by-step guide to integrating Gmail SMTP with Go — app password setup, a reusable mailer package with HTML templates, and a working send example.
If you need to send emails from a Go application, Gmail's SMTP server is one of the simplest options to get running. No third-party email service account needed — just a Google account and a generated app password.
Setting Up a Gmail App Password
Google requires an app password rather than your regular account password when logging in via SMTP. To generate one:
- Go to your Google Account → Security → App passwords (2-Step Verification must be enabled).
- Create a new app password for "Mail".
- Copy the generated password — you'll use it in the config below. Trim any spaces before saving it.
Project Structure
We'll put everything in a mailer package inside a mail/ folder:
mail/
setup.go — SMTP config, singleton initialisation
usecase.go — Mailer struct, body builder, Send method
sample.template — HTML email template
SMTP Setup (mail/setup.go)
The setup is initialised once using sync.Once so the SMTP auth object is created a single time regardless of how many emails you send:
package mailer
import (
"fmt"
"net/smtp"
"sync"
)
type SMTPMailSetup struct {
Server string
Port string
Address string
Sender string
Password string
Auth smtp.Auth
}
var mailSetup *SMTPMailSetup
var once sync.Once
func getSetup() *SMTPMailSetup {
once.Do(func() {
mailSetup = &SMTPMailSetup{
Server: "smtp.gmail.com",
Port: "587",
Sender: "your_email@gmail.com",
Password: "your_app_password",
}
mailSetup.Address = fmt.Sprintf("%s:%s", mailSetup.Server, mailSetup.Port)
mailSetup.Auth = smtp.PlainAuth("", mailSetup.Sender, mailSetup.Password, mailSetup.Server)
})
return mailSetup
}
In a real project, pull Sender and Password from environment variables — never hard-code credentials in source files.
Mailer Usecase (mail/usecase.go)
The Mailer struct exposes a single Send method. Internally it builds an HTML body from a Go template file before handing off to smtp.SendMail:
package mailer
import (
"bytes"
"fmt"
"net/smtp"
"text/template"
)
type Mailer struct {
Body []byte
Setup *SMTPMailSetup
}
func NewSMTPMailer() *Mailer {
return &Mailer{Setup: getSetup()}
}
func (m *Mailer) createBody(subject, htmlTemplate string, data any) (err error) {
var body bytes.Buffer
const mime = "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
body.Write([]byte(fmt.Sprintf("Subject: %s\n%s\n\n", subject, mime)))
t, err := template.ParseFiles(htmlTemplate)
if err != nil {
return
}
err = t.Execute(&body, data)
if err != nil {
return
}
m.Body = body.Bytes()
return nil
}
func (m *Mailer) Send(recipient, subject, tmpl string, data any) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("error on sending email: %v", r)
}
}()
err = m.createBody(subject, tmpl, data)
if err != nil {
return
}
err = smtp.SendMail(m.Setup.Address, m.Setup.Auth, m.Setup.Sender, []string{recipient}, m.Body)
return
}
The data any parameter is passed into the Go template, so you can inject dynamic values — a user's name, a verification link, an order summary — by defining fields in a struct and referencing them with {{ .FieldName }} in the template.
HTML Template (mail/sample.template)
<!DOCTYPE html>
<html>
<body>
Sending email from a Go app!
</body>
</html>
Replace this with your actual email HTML. You can reference any field from the data struct using standard Go template syntax.
Putting It Together (main.go)
package main
import (
"log"
"time"
mailer "yourmodule/mail"
)
func main() {
mail := mailer.NewSMTPMailer()
err := mail.Send(
"recipient@example.com",
"Test Email",
"./mail/sample.template",
nil,
)
if err != nil {
log.Println(err)
}
time.Sleep(30 * time.Second)
}
Tips for Production Use
- Use environment variables for
SenderandPassword— never commit credentials to version control. - Template caching —
template.ParseFilesreads from disk on every call. For high-volume sending, parse templates once at startup and store them in theMailerstruct. - Gmail daily limits — free Gmail accounts are capped at around 500 emails per day via SMTP. For transactional email at scale, consider a dedicated service like SendGrid or Amazon SES.
- TLS —
smtp.SendMailwith port 587 uses STARTTLS automatically, so the connection is encrypted in transit.
The full source code is available at github.com/uhkrowi/go-gmail-mailer.