Skip to content
Support this libraryStar on GitHub

mail logo

Fluent email composition and pluggable delivery for GoForj packages and apps.

Go ReferenceLicense: MITCIGo versionLatest tagCodecovUnit tests (executed count)
mail coveragemailfake coveragemaillog coveragemailmailgun coveragemailpostmark coveragemailresend coveragemailsendgrid coveragemailses coveragemailsmtp coverage

Installation ​

bash
go get github.com/goforj/mail

Quick Start ​

go
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("[email protected]", "Example"),
	)

	err = mailer.Message().
		To("[email protected]", "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:

go
driver, err := mailsmtp.New(mailsmtp.Config{
	Host:     "smtp.gmail.com",
	Port:     587,
	Username: "[email protected]",
	Password: "gmail-app-password",
})

Notes:

  • Use a Google app password, not your normal account password.
  • 587 is the usual STARTTLS port. Use 465 with ForceTLS: true if 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 ​

DriverHTML/TextHeadersTagsMetadataAttachmentsNotes
mailsmtp✓✓xx✓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✓✓xx✓Local/dev inspection only; logs the composed message.
mailfake✓✓✓✓✓Test helper; captures the full portable message.

Delivery Contract ​

Every message is validated before a bundled driver performs I/O. A deliverable message requires:

  • a From recipient (either explicit or supplied by WithDefaultFrom);
  • at least one To, Cc, or Bcc recipient;
  • a subject and at least one text or HTML body;
  • single-address recipient fields, safe custom headers, valid provider metadata keys, and valid attachment MIME metadata.

mail.New requires a driver and panics for a nil or typed-nil driver. This is a construction invariant: use mailfake.New() when a test needs a harmless driver. The zero-value MessageBuilder remains useful for standalone composition and Build; calling Send on an unbound builder returns mail.ErrMissingMailer.

Custom headers cannot replace envelope-owned fields such as From, To, Subject, Content-Type, or MIME-Version, and names must be unique without regard to case. Header, subject, attachment, and metadata validation rejects control characters before data reaches SMTP or multipart encoders.

API-backed drivers return an exported, provider-specific ResponseError for non-2xx responses. It exposes the HTTP status and a safe request ID when the provider supplies one. Provider response bodies are bounded and deliberately omitted from error strings so logs cannot accidentally capture credentials or message content.

SMTP TLS Policy ​

mailsmtp uses opportunistic STARTTLS when the server advertises it. Set ForceTLS: true for implicit TLS, commonly used on port 465.

TLS defaults are explicit:

  • certificate and hostname verification are enabled;
  • ServerName defaults to Config.Host;
  • the minimum protocol version defaults to TLS 1.2;
  • a supplied TLSConfig is cloned during construction, so later caller mutation cannot change a running driver.

Use TLSConfig for a private CA or a stricter minimum version. Disabling verification is not recommended.

Unreleased Compatibility Notes ​

This quality pass deliberately tightens the pre-v1 contract:

  • Message.Validate now returns ErrMissingFrom when no sender remains after defaults;
  • mail.New(nil) and maillog.New(nil) now fail fast by panicking;
  • direct mailfake and maillog sends now honor cancellation and use the same validation contract as network drivers;
  • malformed custom HTTP endpoints fail during driver construction instead of the first send;
  • SMTP rejects negative or out-of-range ports, preserves body whitespace, safely encodes Unicode subjects and filenames, and emits quoted-printable text bodies;
  • SendGrid headers and custom arguments are emitted only inside personalizations, matching its API schema.
  • The root module and mailses are staged as a coordinated v0.3.0 release; publish the root tag before mailses/v0.3.0 so the SES module never depends on a local replacement. The checksum-staging sequence is documented in scripts/RELEASE.md.

GoForj-generated mailers already configure WithDefaultFrom, so their normal send shape is unchanged.

Benchmarks ​

Run make bench to measure all eight Mail drivers against local acceptance fixtures. Run make bench-check to verify source fingerprints, raw samples, and generated README medians.

Median of seven one-second samples, go1.27.2 darwin/arm64, Apple M4 Max, GOMAXPROCS=1. One recipient, text and HTML bodies, and a 4 KiB attachment.

These are local client costs, not a ranking of real provider latency or inbox delivery. API drivers use HTTP acceptance fixtures in separate processes without TLS; SMTP uses local Mailpit. Log serializes to io.Discard; Fake resets every 128 sends, with reset cost included. Fixture-server allocations stay outside the client measurements.

Send return latency ​

Mail Client Send Latency chart

Serial send throughput ​

Mail Client Send Throughput chart

Allocated bytes ​

Mail Client Allocated Bytes chart

Allocations ​

Mail Client Allocations chart

Throughput is the reciprocal of median send latency for a serial client, not a concurrent load test. All charts use linear bars; very small values have a minimum visible width. Driver colors stay consistent across metrics.

Recorded medians
DriverSend returnBytes/opAllocs/op
Fake2.69 µs9,45030
Log3.88 µs10,19234
Mailgun48.05 µs27,414223
Resend52.99 µs33,298113
Postmark53.52 µs33,426116
SendGrid53.60 µs32,911103
SES103.72 µs106,222610
SMTP3.71 ms58,850316

Raw samples · Original Go output · Workload and reproduction

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.

go
fake := mailfake.New()
mailer := mail.New(fake, mail.WithDefaultFrom("[email protected]", "Example"))
_ = mailer.Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Send(context.Background())
fmt.Println(fake.SentCount())
// 1

MessageBuilder.Bcc ​

Bcc appends one blind-carbon-copy recipient.

go
msg, _ := mail.New(mailfake.New()).Message().
	From("[email protected]", "Example").
	To("[email protected]", "Alice").
	Bcc("[email protected]", "Audit").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(msg.Bcc[0].Email)
// [email protected]

MessageBuilder.Cc ​

Cc appends one carbon-copy recipient.

go
msg, _ := mail.New(mailfake.New()).Message().
	From("[email protected]", "Example").
	To("[email protected]", "Alice").
	Cc("[email protected]", "Manager").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(msg.Cc[0].Email)
// [email protected]

MessageBuilder.From ​

From sets the from recipient.

go
msg, _ := mail.New(mailfake.New()).Message().
	From("[email protected]", "Example Team").
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(msg.From.Email)
// [email protected]

MessageBuilder.Message ​

Message returns the currently composed message without applying mailer defaults.

go
msg := mail.New(mailfake.New()).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Message()
fmt.Println(msg.Subject)
// Welcome

MessageBuilder.ReplyTo ​

ReplyTo appends one reply-to recipient.

go
msg, _ := mail.New(mailfake.New()).Message().
	From("[email protected]", "Example").
	To("[email protected]", "Alice").
	ReplyTo("[email protected]", "Support").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(msg.ReplyTo[0].Email)
// [email protected]

MessageBuilder.To ​

To appends one primary recipient.

go
msg, _ := mail.New(mailfake.New()).Message().
	From("[email protected]", "Example").
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(len(msg.To))
// 1

Construction ​

New ​

New creates a Mailer backed by the provided driver. New panics when driver is nil because a Mailer cannot deliver without its required collaborator.

go
fake := mailfake.New()
mailer := mail.New(fake, mail.WithDefaultFrom("[email protected]", "Example"))
fmt.Println(mailer != nil)
// true

Content ​

MessageBuilder.Attach ​

Attach appends one in-memory attachment.

go
msg := mail.New(mailfake.New()).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Attach("report.txt", "text/plain", []byte("hello world")).
	Message()
fmt.Println(msg.Attachments[0].Filename)
// report.txt

MessageBuilder.AttachFile ​

AttachFile loads one attachment from disk and appends it to the message.

go
_ = os.WriteFile("report.txt", []byte("hello world"), 0o644)
defer os.Remove("report.txt")
msg, _ := mail.New(mailfake.New()).Message().
	From("[email protected]", "Example").
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	AttachFile("report.txt").
	Build()
fmt.Println(msg.Attachments[0].Filename)
// report.txt

MessageBuilder.HTML ​

HTML sets the HTML body.

go
msg := mail.New(mailfake.New()).Message().
	To("[email protected]", "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.

go
message, _ := mail.New(mailfake.New()).Message().
	From("[email protected]", "Example").
	To("[email protected]", "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_123

MessageBuilder.Metadata ​

Metadata sets one provider-facing metadata key/value pair.

go
msg := mail.New(mailfake.New()).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Metadata("tenant_id", "tenant_123").
	Message()
fmt.Println(msg.Metadata["tenant_id"])
// tenant_123

MessageBuilder.Subject ​

Subject sets the message subject.

go
msg := mail.New(mailfake.New()).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Message()
fmt.Println(msg.Subject)
// Welcome

MessageBuilder.Tag ​

Tag appends one provider-facing message tag.

go
msg := mail.New(mailfake.New()).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Tag("welcome").
	Message()
fmt.Println(msg.Tags[0])
// welcome

MessageBuilder.Text ​

Text sets the plain text body.

go
msg := mail.New(mailfake.New()).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Message()
fmt.Println(msg.Text)
// hello world

Defaults ​

WithDefaultFrom ​

WithDefaultFrom configures the default from recipient applied when a message omits one.

go
mailer := mail.New(
	mailfake.New(),
	mail.WithDefaultFrom("[email protected]", "Example"),
)
fmt.Println(mailer != nil)
// true

WithDefaultHeader ​

WithDefaultHeader configures a header applied when a message omits that header key.

go
msg, _ := mail.New(
	mailfake.New(),
	mail.WithDefaultFrom("[email protected]", "Example"),
	mail.WithDefaultHeader("X-App", "goforj"),
).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(msg.Headers["X-App"])
// goforj

WithDefaultMetadata ​

WithDefaultMetadata configures metadata applied when a message omits that metadata key.

go
msg, _ := mail.New(
	mailfake.New(),
	mail.WithDefaultFrom("[email protected]", "Example"),
	mail.WithDefaultMetadata("tenant_id", "tenant_123"),
).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(msg.Metadata["tenant_id"])
// tenant_123

WithDefaultReplyTo ​

WithDefaultReplyTo configures the default reply-to recipients applied when a message omits them.

go
mailer := mail.New(
	mailfake.New(),
	mail.WithDefaultFrom("[email protected]", "Example"),
	mail.WithDefaultReplyTo(mail.Recipient{Email: "[email protected]", Name: "Support"}),
)
msg, _ := mailer.Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(msg.ReplyTo[0].Email)
// [email protected]

WithDefaultTag ​

WithDefaultTag configures a tag prepended to every message sent by the mailer.

go
msg, _ := mail.New(
	mailfake.New(),
	mail.WithDefaultFrom("[email protected]", "Example"),
	mail.WithDefaultTag("transactional"),
).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(msg.Tags[0])
// transactional

Delivery ​

Mailer.Send ​

Send validates the message, applies defaults, and delegates delivery to the driver.

go
mailer := mail.New(mailfake.New(), mail.WithDefaultFrom("[email protected]", "Example"))
err := mailer.Send(context.Background(), mail.Message{
	To:      []mail.Recipient{{Email: "[email protected]", Name: "Alice"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(err == nil)
// true

MessageBuilder.Build ​

Build applies defaults, validates, and returns the composed message without sending it.

go
msg, _ := mail.New(
	mailfake.New(),
	mail.WithDefaultFrom("[email protected]", "Example"),
).Message().
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Build()
fmt.Println(msg.From.Email)
// [email protected]

MessageBuilder.Send ​

Send delegates the composed message to the bound mailer.

go
fake := mailfake.New()
_ = mail.New(fake).Message().
	From("[email protected]", "Example").
	To("[email protected]", "Alice").
	Subject("Welcome").
	Text("hello world").
	Send(context.Background())
fmt.Println(fake.SentCount())
// 1

Logging ​

maillog.Driver.Send ​

Send validates the message and writes one JSON log record.

go
var out bytes.Buffer
_ = maillog.New(&out).Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(strings.Contains(out.String(), "\"subject\":\"Welcome\""))
// true

maillog.New ​

New creates a log mail driver that writes one JSON record per sent message. New panics when writer is nil because output is the driver's required collaborator.

go
var out bytes.Buffer
mailer := maillog.New(&out)
_ = mail.New(mailer).Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(strings.Contains(out.String(), "\"subject\":\"Welcome\""))
// true

maillog.WithBodies ​

WithBodies controls whether HTML and text bodies are included in log output.

go
var out bytes.Buffer
mailer := maillog.New(&out, maillog.WithBodies(true))
_ = mail.New(mailer).Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(strings.Contains(out.String(), "\"text\":\"hello world\""))
// true

maillog.WithNow ​

WithNow overrides the timestamp source used by log entries.

go
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: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(strings.Contains(out.String(), "2026-04-19T00:00:00Z"))
// true

Mailgun ​

mailmailgun.Driver.Send ​

Send validates and transmits one message through Mailgun.

go
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: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(err == nil)
// false

mailmailgun.New ​

New creates a Mailgun mail driver from the given config.

go
driver, _ := mailmailgun.New(mailmailgun.Config{
	Domain: "mg.example.com",
	APIKey: "key-test",
})
fmt.Println(driver != nil)
// true

mailmailgun.ResponseError.Error ​

Error formats the provider status and safe correlation identifier without including response content.

Message Model ​

AttachmentFromBytes ​

AttachmentFromBytes creates one attachment from in-memory content.

go
attachment := mail.AttachmentFromBytes("report.txt", "text/plain", []byte("hello world"))
fmt.Println(attachment.Filename)
// report.txt

AttachmentFromPath ​

AttachmentFromPath loads one attachment from a local file path.

go
_ = os.WriteFile("report.txt", []byte("hello world"), 0o644)
defer os.Remove("report.txt")
attachment, _ := mail.AttachmentFromPath("report.txt")
fmt.Println(attachment.Filename)
// report.txt

Message.Clone ​

Clone returns a copy of the message safe for reuse in tests and builders.

go
original := mail.Message{
	To:      []mail.Recipient{{Email: "[email protected]", Name: "Alice"}},
	Subject: "Welcome",
	Text:    "hello world",
}
cloned := original.Clone()
cloned.Subject = "Changed"
fmt.Println(original.Subject)
// Welcome

Message.Validate ​

Validate checks that the message has valid recipients, subject, body, and headers.

go
err := (mail.Message{
	From:    &mail.Recipient{Email: "[email protected]", Name: "Example"},
	To:      []mail.Recipient{{Email: "[email protected]", Name: "Alice"}},
	Subject: "Welcome",
	Text:    "hello world",
}).Validate()
fmt.Println(err == nil)
// true

Postmark ​

mailpostmark.Driver.Send ​

Send validates and transmits one message through Postmark.

go
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: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(err == nil)
// false

mailpostmark.New ​

New creates a Postmark mail driver from the given config.

go
driver, _ := mailpostmark.New(mailpostmark.Config{
	ServerToken: "pm_test_token",
})
fmt.Println(driver != nil)
// true

mailpostmark.ResponseError.Error ​

Error formats provider codes and a safe correlation identifier without including response content.

Resend ​

mailresend.Driver.Send ​

Send validates and transmits one message through Resend.

go
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: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(err == nil)
// false

mailresend.New ​

New creates a Resend mail driver from the given config.

go
driver, _ := mailresend.New(mailresend.Config{
	APIKey: "re_test_key",
})
fmt.Println(driver != nil)
// true

mailresend.ResponseError.Error ​

Error formats the provider status and safe correlation identifier without including response content.

SES ​

mailses.Driver.Send ​

Send validates and transmits one message through Amazon SES.

go
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: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(err == nil)
// false

mailses.New ​

New creates an Amazon SES mail driver from the given config.

go
driver, _ := mailses.New(mailses.Config{
	Region:          "us-east-1",
	AccessKeyID:     "test",
	SecretAccessKey: "test",
})
fmt.Println(driver != nil)
// true

SMTP ​

mailsmtp.Driver.Send ​

Send validates and transmits one message over SMTP.

go
driver, _ := mailsmtp.New(mailsmtp.Config{
	Host: "smtp.example.com",
	Port: 587,
})
err := driver.Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(err == nil)
// false

mailsmtp.New ​

New creates an SMTP mail driver from the given config. TLS defaults to the configured host for ServerName, a minimum of TLS 1.2, and normal certificate verification.

go
driver, _ := mailsmtp.New(mailsmtp.Config{
	Host: "smtp.example.com",
	Port: 587,
})
fmt.Println(driver != nil)
// true

gmail:

go
driver, _ := mailsmtp.New(mailsmtp.Config{
	Host:     "smtp.gmail.com",
	Port:     587,
	Username: "[email protected]",
	Password: "gmail-app-password",
})
fmt.Println(driver != nil)
// true

mailsmtp.Render ​

Render turns one message into an RFC 822 style SMTP payload.

go
raw, _ := mailsmtp.Render(mail.Message{
	From:    &mail.Recipient{Email: "[email protected]", Name: "Example"},
	To:      []mail.Recipient{{Email: "[email protected]", Name: "Alice"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(strings.Contains(string(raw), "Subject: Welcome"))
// true

SendGrid ​

mailsendgrid.Driver.Send ​

Send validates and transmits one message through SendGrid.

go
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: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(err == nil)
// false

mailsendgrid.New ​

New creates a SendGrid mail driver from the given config.

go
driver, _ := mailsendgrid.New(mailsendgrid.Config{
	APIKey: "SG.test_key",
})
fmt.Println(driver != nil)
// true

mailsendgrid.ResponseError.Error ​

Error formats the provider status and safe correlation identifier without including response content.

Testing ​

mailfake.Driver.Last ​

Last returns the last recorded message when one exists.

go
fake := mailfake.New()
_ = mail.New(fake).Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
last, _ := fake.Last()
fmt.Println(last.Subject)
// Welcome

mailfake.Driver.Messages ​

Messages returns a copy of every recorded message.

go
fake := mailfake.New()
_ = mail.New(fake).Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(len(fake.Messages()))
// 1

mailfake.Driver.Reset ​

Reset clears recorded messages and any configured send error.

go
fake := mailfake.New()
_ = fake.Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fake.Reset()
fmt.Println(fake.SentCount())
// 0

mailfake.Driver.Send ​

Send validates and records the message, returning the configured delivery error when set.

go
fake := mailfake.New()
_ = fake.Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(fake.SentCount())
// 1

mailfake.Driver.SentCount ​

SentCount reports the number of recorded messages.

go
fake := mailfake.New()
_ = fake.Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(fake.SentCount())
// 1

mailfake.Driver.SetError ​

SetError configures the error returned by future sends.

go
fake := mailfake.New()
fake.SetError(errors.New("boom"))
err := fake.Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(err != nil)
// true

mailfake.New ​

New creates an in-memory fake mail driver for tests.

go
fake := mailfake.New()
_ = mail.New(fake).Send(context.Background(), mail.Message{
	From:    &mail.Recipient{Email: "[email protected]"},
	To:      []mail.Recipient{{Email: "[email protected]"}},
	Subject: "Welcome",
	Text:    "hello world",
})
fmt.Println(fake.SentCount())
// 1

Development ​

Use make test for root-module tests, make vet for static checks, and make generate to refresh generated documentation. Run make docs-watch to regenerate documentation as source files change. The docs, examples, and mailses directories are separate Go modules and can be tested from their own directories when changed.

Using with GoForj ​

GoForj Apps expose named mailers through generated accessors. Send through those accessors and keep transport selection and credentials in configuration.

For the App workflow, see Mail.