Lightweight email package with multi-provider support (ses, mailgun, mandrill, postmark, resend, sendgrid, smtp)
CI / CD Β Β
|
|
Β Β Β Β Quality Β Β
|
|
Security Β Β
|
|
Β Β Β Β Community Β Β
|
|
πΒ Installation
|
π§ͺΒ ExamplesΒ &Β Tests
|
πΒ Documentation
|
π€Β Contributing
|
π οΈΒ CodeΒ Standards
|
β‘Β Benchmarks
|
π€Β AIΒ Usage
|
βοΈΒ License
|
π₯Β Maintainers
|
go-mail requires a supported release of Go.
go get github.com/mrz1836/go-mailView the generated documentation
package main
import (
"context"
"log"
gomail "github.com/mrz1836/go-mail"
)
func main() {
// Configure the sender and at least one provider
mail := &gomail.MailService{
FromName: "No Reply",
FromUsername: "no-reply",
FromDomain: "example.com",
SendGridAPIKey: "SG.xxxx",
}
if err := mail.StartUp(); err != nil {
log.Fatal(err)
}
// Create and send an email
email := mail.NewEmail()
email.Subject = "Welcome!"
email.HTMLContent = "<p>Thanks for signing up.</p>"
email.PlainTextContent = "Thanks for signing up."
email.Recipients = []string{"Jane Doe <jane@example.com>"}
result, err := mail.Send(context.Background(), email, gomail.SendGrid)
if err != nil {
log.Fatal(err)
}
log.Printf("sent via %s: %s", result.Provider, result.MessageID)
}More complete examples (every provider, attachments, templates, failover and provider options) are in examples/examples.go.
- Supports multiple service providers (below), plus your own through the
Providerinterface - Failover across providers, with the provider's message id returned from
Send - Provider-specific features through typed options (ie: Postmark message streams, SendGrid templates, Mailgun test mode)
- AWS SES via static keys or the default credential chain (IAM role)
- SMTP with STARTTLS or implicit TLS, optional authentication, and timeouts
- Plain-text and HTML content
- Recipients with display names (
Jane Doe <jane@example.com>); duplicates acrossTo,CCandBCCare removed - Multiple file attachments, plus inline (
cid:) images - Custom headers, including one-click
List-Unsubscribe(required by Gmail and Yahoo for bulk senders) - Tags, metadata, scheduled sending and idempotency keys (provider dependant)
- Open & click tracking (provider dependant)
- Inject css into html content
- Basic template support
- Max restrictions on
To,CCandBCC - Secrets are redacted when the configuration is logged or marshaled to JSON
BCCrecipients are never written into the message headers, and header values cannot inject new headers
Supported Service Providers
- AWS SES (tags & metadata become message tags; tracking via a configuration set)
- Mailgun (up to 10 tags; metadata becomes user variables; US or EU region)
- Mandrill (recipients are not preserved: each
Torecipient only sees their own address) - Postmark (one tag per email: multiple tags are joined with a comma)
- Resend (open & click tracking configured per domain)
- SendGrid (native open & click tracking)
- SMTP
| Feature | AWS SES | Mailgun | Mandrill | Postmark | Resend | SendGrid | SMTP |
|---|---|---|---|---|---|---|---|
| Tags | β | β | β | β | β | β | |
| Metadata | β | β | β | β | β | β | |
| Open / click tracking | β | β | β | β | |||
Scheduled send (SendAt) |
β | β | β | β | |||
| Idempotency key | β | ||||||
| Auto text | β | β | |||||
| View content link | β |
When an email uses a feature its provider does not support, go-mail logs a
warning (or returns ErrUnsupportedFeature when StrictFeatures is set). An
unsupported SendAt is always an error, so a scheduled email is never sent early.
Mailgun detects an attachment's content type from its file name and uses the
file name as the content id of an inline image, so inline attachments are sent
named by their ContentID: use a content id with an extension (ie: logo.png,
referenced as cid:logo.png).
Sending, Failover & Results
// Send returns the provider that accepted the email and its message id
result, err := mail.Send(ctx, email, gomail.Postmark, gomail.SendGrid) // tries Postmark, then SendGrid
if err != nil {
return err
}
log.Printf("sent via %s: %s", result.Provider, result.MessageID)
// SendEmail is still available when only the error matters
err = mail.SendEmail(ctx, email, gomail.SMTP)Every send is bounded by SendTimeout (default one minute) and honors the
context. Attachments added with a reader are buffered on the first send, so the
same email can be retried or failed over.
Provider-Specific Features
Each provider has a typed option that edits its native request right before it is sent, so anything the provider SDK supports is available. Options for other providers are ignored.
email.With(
gomail.PostmarkOption(func(e *postmark.Email) { e.MessageStream = "broadcast" }),
gomail.SendGridOption(func(m *mail.SGMailV3) { m.SetTemplateID("d-123") }),
gomail.ResendOption(func(r *resend.SendEmailRequest) { r.TopicId = "topic_123" }),
gomail.MailgunOption(func(m *mailgun.PlainMessage) { m.SetRequireTLS(true) }),
gomail.MandrillOption(func(m *gochimp.Message) { m.Subaccount = "tenant-1" }),
gomail.SESOption(func(in *ses.SendRawEmailInput) { in.FromArn = aws.String(arn) }),
)Custom Providers & Clients
Register any Provider (a new service, a built-in provider with your own
client, or a fake in tests). A registered provider is kept by StartUp.
// A custom provider
const SparkPost gomail.ServiceProvider = 100
err := mail.RegisterProvider(SparkPost, mySparkPostProvider)
// A built-in provider with a custom client (ie: SendGrid EU data residency)
client := sendgrid.NewSendClient(apiKey)
client.Request, _ = sendgrid.SetDataResidency(client.Request, "eu")
err = mail.RegisterProvider(gomail.SendGrid, gomail.NewSendGridProvider(client))
// A fake provider in your tests
err = mail.RegisterProvider(gomail.SMTP, fakeProvider)Templates
htmlTemplate, _ := email.ParseHTMLTemplate("welcome.html") // {{.Styles}} is replaced with email.CSS, which is inlined
textTemplate, _ := email.ParseTextTemplate("welcome.txt") // text/template: no HTML escaping
err := email.ApplyTemplates(htmlTemplate, textTemplate, data)Configuration
A provider is loaded by StartUp for every service whose credentials are set.
| Field | Description |
|---|---|
FromName, FromUsername, FromDomain |
Default sender (FromUsername and FromDomain are required) |
AwsSesAccessID, AwsSesSecretKey |
AWS SES static credentials |
AwsSesUseIAMRole |
Load AWS SES from the default credential chain instead of static keys |
AwsSesRegion, AwsSesEndpoint, AwsSesConfigurationSet |
AWS SES region (default us-east-1), custom endpoint, and configuration set |
MailgunAPIKey, MailgunDomain |
Mailgun credentials and sending domain (defaults to the domain of the from address) |
MailgunAPIBase |
Mailgun API base URL (default US region; mailgun.APIBaseEU for the EU region) |
MandrillAPIKey, PostmarkServerToken |
Mandrill and Postmark credentials |
ResendAPIKey, SendGridAPIKey |
Resend and SendGrid credentials |
SMTPHost, SMTPPort, SMTPUsername, SMTPPassword |
SMTP server (port defaults to 587; leave the username empty for a relay without auth) |
SMTPImplicitTLS |
Connect with TLS from the start (always on for port 465) |
AutoText, Important, TrackClicks, TrackOpens |
Defaults copied to every email created by NewEmail |
EmailCSS |
Default CSS copied to every email (used by ParseHTMLTemplate) |
MaxToRecipients, MaxCcRecipients, MaxBccRecipients |
Recipient limits (default 50 each) |
MaxAttachmentSize |
Total attachment bytes per email (default 40 MiB, negative for no limit) |
SendTimeout |
Maximum time for one provider send (default one minute, negative for no timeout) |
StrictFeatures |
Return ErrUnsupportedFeature instead of logging a warning for unsupported features |
Logger |
*slog.Logger for warnings (default slog.Default()) |
SMTP
- Every send opens a new connection that honors the context deadline and cancellation
- STARTTLS is used whenever the server offers it; set
SMTPImplicitTLS(or port465) for SMTPS - Server certificates are verified; credentials are never sent over an unencrypted connection (except to localhost)
PLAINauthentication is used, withLOGINas a fallback for servers that only offerLOGIN(ie: Microsoft 365)- Use
NewSMTPProviderwithRegisterProviderfor a customtls.Configor EHLO name
Development Setup (Getting Started)
Install MAGE-X build tool for development:
# Install MAGE-X for development and building
go install github.com/mrz1836/mage-x/cmd/magex@latest
magex update:installLibrary Deployment
This project uses goreleaser for streamlined binary and library deployment to GitHub. To get started, install it via:
brew install goreleaserThe release process is defined in the .goreleaser.yml configuration file.
Then create and push a new Git tag using:
magex version:bump bump=patch push=true branch=masterThis process ensures consistent, repeatable releases with properly versioned artifacts and citation metadata.
Build Commands
View all build commands
magex helpGitHub Workflows
All workflows are driven by modular configuration in .github/env/ β no YAML editing required.
Updating Dependencies
To update all dependencies (Go modules, linters, and related tools), run:
magex deps:updateThis command ensures all dependencies are brought up to date in a single step, including Go modules and any managed tools. It is the recommended way to keep your development environment and CI in sync with the latest versions.
All unit tests and fuzz tests run via GitHub Actions and use Go version 1.26.x. View the configuration file.
Run all tests (fast):
magex testRun all tests with race detector (slower):
magex test:raceRun the fuzz tests:
magex test:fuzzRun the Go benchmarks:
magex benchRead more about this Go project's code standards.
Read the AI Usage & Assistant Guidelines for details on how AI is used in this project and how to interact with AI assistants.
![]() |
|---|
| MrZ |
View the contributing guidelines and please follow the code of conduct.
All kinds of contributions are welcome π! The most basic way to show your support is to star π the project, or to raise issues π¬. You can also support this project by becoming a sponsor on GitHub π or by making a bitcoin donation to ensure this journey continues indefinitely! π
