Fluent email composition and pluggable delivery for GoForj packages and apps.
Using With GoForj Apps
Generated Apps resolve mail through the generated mail manager, provider wiring, auth delivery integration, metrics, and inspects. Start with Auth, Configuration, and Environment Variables when mail is part of a full App.
Use this page when you need standalone message composition, driver constructors, delivery behavior, or fakes for tests.
Installation
go get github.com/goforj/mailQuick Start
package main
import (
"context"
"log"
"github.com/goforj/mail"
"github.com/goforj/mail/mailsmtp"
)
func main() {
driver, err := mailsmtp.New(mailsmtp.Config{
Host: "smtp.example.com",
Port: 587,
Username: "smtp-user",
Password: "smtp-password",
})
if err != nil {
log.Fatal(err)
}
mailer := mail.New(
driver,
mail.WithDefaultFrom("no-reply@example.com", "Example"),
)
err = mailer.Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Send(context.Background())
if err != nil {
log.Fatal(err)
}
}Gmail via SMTP
Gmail does not need its own driver. Use mailsmtp with Gmail's SMTP host and an app password:
driver, err := mailsmtp.New(mailsmtp.Config{
Host: "smtp.gmail.com",
Port: 587,
Username: "you@gmail.com",
Password: "gmail-app-password",
})Notes:
- Use a Google app password, not your normal account password.
587is the usual STARTTLS port. Use465withForceTLS: trueif you explicitly want implicit TLS.- Gmail is fine for personal or low-volume transactional sending, but a dedicated provider like Resend, Postmark, Mailgun, or SendGrid is usually a better production default.
Driver Capabilities
| Driver | HTML/Text | Headers | Tags | Metadata | Attachments | Notes |
|---|---|---|---|---|---|---|
| mailsmtp | ✓ | ✓ | x | x | ✓ | Covers Gmail and other SMTP providers. |
| mailresend | ✓ | ✓ | ✓ | ✓ | ✓ | API-backed transactional delivery. |
| mailpostmark | ✓ | ✓ | ✓ | ✓ | ✓ | First tag is native; additional tags are mapped into metadata. |
| mailmailgun | ✓ | ✓ | ✓ | ✓ | ✓ | Uses Mailgun multipart message uploads. |
| mailsendgrid | ✓ | ✓ | ✓ | ✓ | ✓ | Maps tags to categories and metadata to custom args. |
| mailses | ✓ | ✓ | ✓ | ✓ | ✓ | Uses SES raw email with the same MIME rendering as SMTP. |
| maillog | ✓ | ✓ | x | x | ✓ | Local/dev inspection only; logs the composed message. |
| mailfake | ✓ | ✓ | ✓ | ✓ | ✓ | Test helper; captures the full portable message. |
API
API Index
API Reference
Generated from public API comments and examples.
Composition
Mailer.Message
Message starts a new fluent message builder bound to this mailer.
fake := mailfake.New()
mailer := mail.New(fake, mail.WithDefaultFrom("no-reply@example.com", "Example"))
_ = mailer.Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Send(context.Background())
fmt.Println(fake.SentCount())
// 1MessageBuilder.Bcc
Bcc appends one blind-carbon-copy recipient.
msg, _ := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Bcc("audit@example.com", "Audit").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(msg.Bcc[0].Email)
// audit@example.comMessageBuilder.Cc
Cc appends one carbon-copy recipient.
msg, _ := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Cc("manager@example.com", "Manager").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(msg.Cc[0].Email)
// manager@example.comMessageBuilder.From
From sets the from recipient.
msg, _ := mail.New(mailfake.New()).Message().
From("team@example.com", "Example Team").
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(msg.From.Email)
// team@example.comMessageBuilder.Message
Message returns the currently composed message without applying mailer defaults.
msg := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Message()
fmt.Println(msg.Subject)
// WelcomeMessageBuilder.ReplyTo
ReplyTo appends one reply-to recipient.
msg, _ := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
ReplyTo("support@example.com", "Support").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(msg.ReplyTo[0].Email)
// support@example.comMessageBuilder.To
To appends one primary recipient.
msg, _ := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(len(msg.To))
// 1Construction
New
New creates a Mailer backed by the provided driver.
fake := mailfake.New()
mailer := mail.New(fake, mail.WithDefaultFrom("no-reply@example.com", "Example"))
fmt.Println(mailer != nil)
// trueContent
MessageBuilder.Attach
Attach appends one in-memory attachment.
msg := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Attach("report.txt", "text/plain", []byte("hello world")).
Message()
fmt.Println(msg.Attachments[0].Filename)
// report.txtMessageBuilder.AttachFile
AttachFile loads one attachment from disk and appends it to the message.
_ = os.WriteFile("report.txt", []byte("hello world"), 0o644)
defer os.Remove("report.txt")
msg, _ := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
AttachFile("report.txt").
Build()
fmt.Println(msg.Attachments[0].Filename)
// report.txtMessageBuilder.HTML
HTML sets the HTML body.
msg := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
HTML("<p>hello world</p>").
Message()
fmt.Println(msg.HTML)
// <p>hello world</p>MessageBuilder.Header
Header sets or replaces one message header.
message, _ := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Header("X-Request-ID", "req_123").
Tag("welcome").
Metadata("tenant_id", "tenant_123").
Build()
fmt.Println(message.Headers["X-Request-ID"])
// req_123MessageBuilder.Metadata
Metadata sets one provider-facing metadata key/value pair.
msg := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Metadata("tenant_id", "tenant_123").
Message()
fmt.Println(msg.Metadata["tenant_id"])
// tenant_123MessageBuilder.Subject
Subject sets the message subject.
msg := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Message()
fmt.Println(msg.Subject)
// WelcomeMessageBuilder.Tag
Tag appends one provider-facing message tag.
msg := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Tag("welcome").
Message()
fmt.Println(msg.Tags[0])
// welcomeMessageBuilder.Text
Text sets the plain text body.
msg := mail.New(mailfake.New()).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Message()
fmt.Println(msg.Text)
// hello worldDefaults
WithDefaultFrom
WithDefaultFrom configures the default from recipient applied when a message omits one.
mailer := mail.New(
mailfake.New(),
mail.WithDefaultFrom("no-reply@example.com", "Example"),
)
fmt.Println(mailer != nil)
// trueWithDefaultHeader
WithDefaultHeader configures a header applied when a message omits that header key.
msg, _ := mail.New(
mailfake.New(),
mail.WithDefaultHeader("X-App", "goforj"),
).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(msg.Headers["X-App"])
// goforjWithDefaultMetadata
WithDefaultMetadata configures metadata applied when a message omits that metadata key.
msg, _ := mail.New(
mailfake.New(),
mail.WithDefaultMetadata("tenant_id", "tenant_123"),
).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(msg.Metadata["tenant_id"])
// tenant_123WithDefaultReplyTo
WithDefaultReplyTo configures the default reply-to recipients applied when a message omits them.
mailer := mail.New(
mailfake.New(),
mail.WithDefaultReplyTo(mail.Recipient{Email: "support@example.com", Name: "Support"}),
)
msg, _ := mailer.Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(msg.ReplyTo[0].Email)
// support@example.comWithDefaultTag
WithDefaultTag configures a tag prepended to every message sent by the mailer.
msg, _ := mail.New(
mailfake.New(),
mail.WithDefaultTag("transactional"),
).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(msg.Tags[0])
// transactionalDelivery
Mailer.Send
Send validates the message, applies defaults, and delegates delivery to the driver.
mailer := mail.New(mailfake.New(), mail.WithDefaultFrom("no-reply@example.com", "Example"))
err := mailer.Send(context.Background(), mail.Message{
To: []mail.Recipient{{Email: "alice@example.com", Name: "Alice"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(err == nil)
// trueMessageBuilder.Build
Build applies defaults, validates, and returns the composed message without sending it.
msg, _ := mail.New(
mailfake.New(),
mail.WithDefaultFrom("no-reply@example.com", "Example"),
).Message().
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Build()
fmt.Println(msg.From.Email)
// no-reply@example.comMessageBuilder.Send
Send delegates the composed message to the bound mailer.
fake := mailfake.New()
_ = mail.New(fake).Message().
From("no-reply@example.com", "Example").
To("alice@example.com", "Alice").
Subject("Welcome").
Text("hello world").
Send(context.Background())
fmt.Println(fake.SentCount())
// 1Logging
maillog.Driver.Send
Send writes one JSON log record for the message.
var out bytes.Buffer
_ = maillog.New(&out).Send(context.Background(), mail.Message{
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(strings.Contains(out.String(), "\"subject\":\"Welcome\""))
// truemaillog.New
New creates a log mail driver that writes one JSON record per sent message.
var out bytes.Buffer
mailer := maillog.New(&out)
_ = mail.New(mailer).Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(strings.Contains(out.String(), "\"subject\":\"Welcome\""))
// truemaillog.WithBodies
WithBodies controls whether HTML and text bodies are included in log output.
var out bytes.Buffer
mailer := maillog.New(&out, maillog.WithBodies(true))
_ = mail.New(mailer).Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(strings.Contains(out.String(), "\"text\":\"hello world\""))
// truemaillog.WithNow
WithNow overrides the timestamp source used by log entries.
var out bytes.Buffer
mailer := maillog.New(&out, maillog.WithNow(func() time.Time {
return time.Date(2026, time.April, 19, 0, 0, 0, 0, time.UTC)
}))
_ = mail.New(mailer).Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(strings.Contains(out.String(), "2026-04-19T00:00:00Z"))
// trueMailgun
mailmailgun.Driver.Send
Send validates and transmits one message through Mailgun.
driver, _ := mailmailgun.New(mailmailgun.Config{
Domain: "mg.example.com",
APIKey: "key-test",
Endpoint: "http://127.0.0.1:1",
})
err := driver.Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(err == nil)
// falsemailmailgun.New
New creates a Mailgun mail driver from the given config.
driver, _ := mailmailgun.New(mailmailgun.Config{
Domain: "mg.example.com",
APIKey: "key-test",
})
fmt.Println(driver != nil)
// trueMessage Model
AttachmentFromBytes
AttachmentFromBytes creates one attachment from in-memory content.
attachment := mail.AttachmentFromBytes("report.txt", "text/plain", []byte("hello world"))
fmt.Println(attachment.Filename)
// report.txtAttachmentFromPath
AttachmentFromPath loads one attachment from a local file path.
_ = os.WriteFile("report.txt", []byte("hello world"), 0o644)
defer os.Remove("report.txt")
attachment, _ := mail.AttachmentFromPath("report.txt")
fmt.Println(attachment.Filename)
// report.txtMessage.Clone
Clone returns a copy of the message safe for reuse in tests and builders.
original := mail.Message{
To: []mail.Recipient{{Email: "alice@example.com", Name: "Alice"}},
Subject: "Welcome",
Text: "hello world",
}
cloned := original.Clone()
cloned.Subject = "Changed"
fmt.Println(original.Subject)
// WelcomeMessage.Validate
Validate checks that the message has valid recipients, subject, body, and headers.
err := (mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com", Name: "Example"},
To: []mail.Recipient{{Email: "alice@example.com", Name: "Alice"}},
Subject: "Welcome",
Text: "hello world",
}).Validate()
fmt.Println(err == nil)
// truePostmark
mailpostmark.Driver.Send
Send validates and transmits one message through Postmark.
driver, _ := mailpostmark.New(mailpostmark.Config{
ServerToken: "pm_test_token",
Endpoint: "http://127.0.0.1:1",
})
err := driver.Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(err == nil)
// falsemailpostmark.New
New creates a Postmark mail driver from the given config.
driver, _ := mailpostmark.New(mailpostmark.Config{
ServerToken: "pm_test_token",
})
fmt.Println(driver != nil)
// trueResend
mailresend.Driver.Send
Send validates and transmits one message through Resend.
driver, _ := mailresend.New(mailresend.Config{
APIKey: "re_test_key",
Endpoint: "http://127.0.0.1:1",
})
err := driver.Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(err == nil)
// falsemailresend.New
New creates a Resend mail driver from the given config.
driver, _ := mailresend.New(mailresend.Config{
APIKey: "re_test_key",
})
fmt.Println(driver != nil)
// trueSES
mailses.Driver.Send
Send validates and transmits one message through Amazon SES.
driver, _ := mailses.New(mailses.Config{
Region: "us-east-1",
AccessKeyID: "test",
SecretAccessKey: "test",
Endpoint: "http://127.0.0.1:1",
})
err := driver.Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(err == nil)
// falsemailses.New
New creates an Amazon SES mail driver from the given config.
driver, _ := mailses.New(mailses.Config{
Region: "us-east-1",
AccessKeyID: "test",
SecretAccessKey: "test",
})
fmt.Println(driver != nil)
// trueSMTP
mailsmtp.Driver.Send
Send validates and transmits one message over SMTP.
driver, _ := mailsmtp.New(mailsmtp.Config{
Host: "smtp.example.com",
Port: 587,
})
err := driver.Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(err == nil)
// falsemailsmtp.New
New creates an SMTP mail driver from the given config.
driver, _ := mailsmtp.New(mailsmtp.Config{
Host: "smtp.example.com",
Port: 587,
})
fmt.Println(driver != nil)
// truegmail:
driver, _ := mailsmtp.New(mailsmtp.Config{
Host: "smtp.gmail.com",
Port: 587,
Username: "you@gmail.com",
Password: "gmail-app-password",
})
fmt.Println(driver != nil)
// truemailsmtp.Render
Render turns one message into an RFC 822 style SMTP payload.
raw, _ := mailsmtp.Render(mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com", Name: "Example"},
To: []mail.Recipient{{Email: "alice@example.com", Name: "Alice"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(strings.Contains(string(raw), "Subject: Welcome"))
// trueSendGrid
mailsendgrid.Driver.Send
Send validates and transmits one message through SendGrid.
driver, _ := mailsendgrid.New(mailsendgrid.Config{
APIKey: "SG.test_key",
Endpoint: "http://127.0.0.1:1",
})
err := driver.Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(err == nil)
// falsemailsendgrid.New
New creates a SendGrid mail driver from the given config.
driver, _ := mailsendgrid.New(mailsendgrid.Config{
APIKey: "SG.test_key",
})
fmt.Println(driver != nil)
// trueTesting
mailfake.Driver.Last
Last returns the last recorded message when one exists.
fake := mailfake.New()
_ = mail.New(fake).Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
last, _ := fake.Last()
fmt.Println(last.Subject)
// Welcomemailfake.Driver.Messages
Messages returns a copy of every recorded message.
fake := mailfake.New()
_ = mail.New(fake).Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(len(fake.Messages()))
// 1mailfake.Driver.Reset
Reset clears recorded messages and any configured send error.
fake := mailfake.New()
_ = fake.Send(context.Background(), mail.Message{
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fake.Reset()
fmt.Println(fake.SentCount())
// 0mailfake.Driver.Send
Send records the message and returns the configured error when set.
fake := mailfake.New()
_ = fake.Send(context.Background(), mail.Message{
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(fake.SentCount())
// 1mailfake.Driver.SentCount
SentCount reports the number of recorded messages.
fake := mailfake.New()
_ = fake.Send(context.Background(), mail.Message{
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(fake.SentCount())
// 1mailfake.Driver.SetError
SetError configures the error returned by future sends.
fake := mailfake.New()
fake.SetError(errors.New("boom"))
err := fake.Send(context.Background(), mail.Message{
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(err != nil)
// truemailfake.New
New creates an in-memory fake mail driver for tests.
fake := mailfake.New()
_ = mail.New(fake).Send(context.Background(), mail.Message{
From: &mail.Recipient{Email: "no-reply@example.com"},
To: []mail.Recipient{{Email: "alice@example.com"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(fake.SentCount())
// 1Docs Tooling
go run ./docs/examplegen/main.gogo run ./docs/readme/main.gogo run ./docs/readme/testcounts/main.go./docs/watcher.sh
