Home Blog Sending Email Using Go and Gmail
Development 5 min read

Sending Email Using Go and Gmail

Nurul

Nurul

Lead & Fullstack Engineer · April 5, 2024

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:

  1. Go to your Google Account → Security → App passwords (2-Step Verification must be enabled).
  2. Create a new app password for "Mail".
  3. 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 Sender and Password — never commit credentials to version control.
  • Template cachingtemplate.ParseFiles reads from disk on every call. For high-volume sending, parse templates once at startup and store them in the Mailer struct.
  • 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.
  • TLSsmtp.SendMail with 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.

Author

Nurul

Nurul

Lead & Fullstack Engineer

Part of the SimpleFunc team building clean, scalable solutions for businesses across Indonesia.

Work With Us

Have a project in mind? Let's talk about how we can help build it.

Get in Touch