Beranda 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.

Penulis

Nurul

Nurul

Lead & Fullstack Engineer

Bagian dari tim SimpleFunc yang membangun solusi bersih dan skalabel untuk bisnis di seluruh Indonesia.

Kerja Sama

Punya proyek? Mari kita diskusikan bagaimana kami bisa membantu membangunnya.

Hubungi Kami