Go’s standard library includes net/smtp for sending email, but real-world applications need more — attachment support, HTML templates, transactional email service integration, and robust error handling. This article compares four popular Go email libraries: Gomail, Mailgun-Go, SendGrid-Go, and Go-Simple-Mail, helping you choose the right tool for your application.

Comparison at a Glance

FeatureGomailMailgun-GoSendGrid-GoGo-Simple-Mail
GitHub Stars4,7367441,057695
SMTP SupportYesNo (API only)No (API only)Yes
HTML EmailYesYesYesYes
AttachmentsYesYesYesYes
CC/BCCYesVia APIVia APIYes
Connection PoolingYesN/AN/AYes
Last Updated2023202620262024
Dependencynet/smtpHTTP clientHTTP clientnet/smtp

Deep Dive: Each Library

Gomail — The Swiss Army Knife

Gomail is the most popular Go email library, offering a complete SMTP client with attachment handling, embedded images, and both text and HTML body support. It wraps Go’s net/smtp with a friendlier API.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
    "gopkg.in/gomail.v2"
)

func main() {
    m := gomail.NewMessage()
    m.SetHeader("From", "sender@example.com")
    m.SetHeader("To", "recipient@example.com")
    m.SetHeader("Subject", "Hello from Gomail!")
    m.SetBody("text/html", "<h1>Hello</h1><p>This is an HTML email.</p>")
    m.Attach("/path/to/report.pdf")

    d := gomail.NewDialer("smtp.example.com", 587, "username", "password")
    if err := d.DialAndSend(m); err != nil {
        panic(err)
    }
}

Gomail is ideal for applications that control their own SMTP server or use a third-party SMTP relay. Its connection pooling reduces overhead when sending bulk emails. However, it hasn’t seen updates since 2023, which is worth considering for long-term maintenance.

Mailgun-Go — API-First Transactional Email

Mailgun-Go is the official Go SDK for Mailgun’s email API service. Unlike SMTP-based libraries, it communicates directly with Mailgun’s HTTP API for sending, tracking, and validating emails.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
    "context"
    "github.com/mailgun/mailgun-go/v4"
)

func main() {
    mg := mailgun.NewMailgun("example.com", "your-api-key")
    message := mg.NewMessage(
        "sender@example.com",
        "Welcome!",
        "Hello, welcome to our service!",
        "recipient@example.com",
    )
    message.SetHtml("<h1>Welcome</h1><p>Thanks for signing up!</p>")

    ctx := context.Background()
    _, id, err := mg.Send(ctx, message)
    if err != nil {
        panic(err)
    }
    println("Message sent with ID:", id)
}

Mailgun-Go provides delivery tracking, webhook event handling, and spam complaint management — features you’d need to build yourself with SMTP-based libraries. The trade-off is vendor lock-in: you must use Mailgun’s paid service.

SendGrid-Go — Enterprise Transactional Email

SendGrid-Go is Twilio’s official Go library for the SendGrid email platform, offering template-based sending, dynamic content substitution, and comprehensive delivery analytics.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
    "github.com/sendgrid/sendgrid-go"
    "github.com/sendgrid/sendgrid-go/helpers/mail"
)

func main() {
    from := mail.NewEmail("Sender", "sender@example.com")
    to := mail.NewEmail("Recipient", "recipient@example.com")
    subject := "Your Order Confirmation"
    plainText := "Thank you for your order!"
    htmlContent := "<h1>Order Confirmed</h1><p>Thank you for your order!</p>"
    message := mail.NewSingleEmail(from, subject, to, plainText, htmlContent)

    client := sendgrid.NewSendClient("your-sendgrid-api-key")
    response, err := client.Send(message)
    if err != nil {
        panic(err)
    }
    println("Status:", response.StatusCode)
}

SendGrid-Go integrates with SendGrid’s template engine — you design templates in the SendGrid dashboard and populate them via API, keeping email design separate from application code. It also supports dynamic template data for personalized bulk emails.

Go-Simple-Mail — Lightweight SMTP with Extras

Go-Simple-Mail is a lightweight SMTP client that supports keep-alive connections, TLS/SSL encryption, and both text and HTML bodies. It strikes a balance between Gomail’s full-featured approach and raw net/smtp.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
    "github.com/xhit/go-simple-mail/v2"
)

func main() {
    server := mail.NewSMTPClient()
    server.Host = "smtp.example.com"
    server.Port = 587
    server.Username = "username"
    server.Password = "password"
    server.Encryption = mail.EncryptionSTARTTLS

    smtpClient, err := server.Connect()
    if err != nil {
        panic(err)
    }

    email := mail.NewMSG()
    email.SetFrom("sender@example.com").
        AddTo("recipient@example.com").
        SetSubject("Test Email")

    email.SetBody(mail.TextHTML, "<h1>Hello from go-simple-mail!</h1>")
    email.Attach(&mail.File{FilePath: "/path/to/file.pdf"})

    if err := email.Send(smtpClient); err != nil {
        panic(err)
    }
}

Go-Simple-Mail’s keep-alive connection support makes it efficient for applications that send frequent emails — the connection is reused rather than re-established for each message.

When to Use Each

Choose Gomail if you need a battle-tested SMTP library with full attachment and embedded image support, and don’t mind a stable but unmaintained codebase.

Choose Mailgun-Go or SendGrid-Go if you’re already using those email services or want delivery analytics, template management, and bounce handling without building infrastructure yourself.

Choose Go-Simple-Mail if you want an actively maintained SMTP client with modern features like keep-alive connections and TLS support.

Why Self-Host Your Email with Go SMTP Libraries?

Many developers default to third-party email APIs, but self-hosting email sending with SMTP libraries gives you complete control over your data pipeline. When you use Go SMTP libraries like Gomail or Go-Simple-Mail, your email content never passes through a third-party server beyond your configured relay — important for applications handling sensitive notifications or healthcare data.

For transactional emails at scale, combining an SMTP library with a self-hosted SMTP relay like Postal or Haraka can save thousands in API fees. Our SMTP relay pool management guide covers setting up your own relay infrastructure. If you need to handle bounce notifications (DSNs) from your Go email code, see our SMTP bounce management guide.

For applications building out their Go microservice stack, check our comparisons of Go task queue libraries and Go serialization libraries — both common companions to email-sending services.

Error Handling and Delivery Monitoring

Email delivery is inherently unreliable — networks fail, SMTP servers reject messages, and rate limits kick in. Each library handles errors differently, and understanding these patterns is critical for production use.

Gomail returns explicit error values from DialAndSend(). Common errors include SMTP authentication failures, recipient rejection (550), and connection timeouts. You should wrap DialAndSend in a retry loop with exponential backoff:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func sendWithRetry(dialer *gomail.Dialer, msg *gomail.Message, maxRetries int) error {
    for i := 0; i < maxRetries; i++ {
        if err := dialer.DialAndSend(msg); err != nil {
            if i == maxRetries-1 {
                return fmt.Errorf("failed after %d retries: %w", maxRetries, err)
            }
            time.Sleep(time.Duration(math.Pow(2, float64(i))) * time.Second)
            continue
        }
        return nil
    }
    return nil
}

Mailgun-Go and SendGrid-Go return structured error types that include HTTP status codes and API-specific error messages. You can inspect these to determine whether to retry (429 rate limit, 5xx server error) or fail permanently (4xx client error). Both also provide webhook endpoints for asynchronous delivery status tracking — implement a webhook handler to log bounces, spam reports, and delivery confirmations.

Go-Simple-Mail’s Send() returns a standard Go error. Since it wraps net/smtp, you get the same error types as the standard library. Use errors.As() to check for specific SMTP error codes and implement conditional retry logic.

For production monitoring, instrument all email-sending code paths with metrics — count sent messages, track error rates by type, and alert on delivery failure spikes. OpenTelemetry instrumentation can trace email delivery end-to-end from your application through the SMTP relay to the final inbox. For security-conscious deployments, always validate sender addresses before passing them to email libraries to prevent header injection attacks. Mailgun-Go and SendGrid-Go handle this through their API’s server-side validation, but SMTP-based libraries like Gomail and Go-Simple-Mail require explicit input sanitization — strip newline characters from user-provided headers and validate email formats before calling Send().

FAQ

Which Go email library is best for a small REST API?

For a small REST API that needs to send a few emails, Go-Simple-Mail is the best choice. It’s lightweight, actively maintained, and supports both TLS and keep-alive connections. Its API is straightforward and doesn’t require learning a service-specific SDK.

Can I use Gomail with Gmail SMTP?

Yes. Configure the dialer with smtp.gmail.com on port 587 with STARTTLS. You’ll need an App Password rather than your regular Gmail password if 2-factor authentication is enabled. Gomail’s NewDialer handles this configuration cleanly.

How do Mailgun-Go and SendGrid-Go handle email delivery failures?

Both libraries provide webhook endpoints your application can listen to for delivery events — bounces, drops, spam reports, opens, and clicks. Mailgun-Go’s mailgun.ListEvents() and SendGrid-Go’s Event Webhook both allow you to programmatically track delivery status, something SMTP libraries cannot do natively.

Are there any self-hosted alternatives to Mailgun and SendGrid?

Yes. You can self-host a full email stack using Postal, Haraka, or Stalwart Mail as your SMTP relay, then use Gomail or Go-Simple-Mail as the SMTP client in your Go application. This gives you complete data ownership with zero per-email API costs.

Is Gomail still safe to use given it hasn’t been updated since 2023?

Gomail is stable and widely used in production. Its core functionality (SMTP sending) hasn’t changed because the SMTP protocol is stable. However, review its dependency tree for any vulnerable sub-dependencies and consider forking if you need active maintenance guarantees.


💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com