
Fluent email composition and pluggable delivery for GoForj packages and apps.
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("[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:
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.
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. |
Delivery Contract
Every message is validated before a bundled driver performs I/O. A deliverable message requires:
- a
Fromrecipient (either explicit or supplied byWithDefaultFrom); - at least one
To,Cc, orBccrecipient; - 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;
ServerNamedefaults toConfig.Host;- the minimum protocol version defaults to TLS 1.2;
- a supplied
TLSConfigis 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.Validatenow returnsErrMissingFromwhen no sender remains after defaults;mail.New(nil)andmaillog.New(nil)now fail fast by panicking;- direct
mailfakeandmaillogsends 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
mailsesare staged as a coordinated v0.3.0 release; publish the root tag beforemailses/v0.3.0so 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
Serial send throughput
Allocated bytes
Allocations
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
| Driver | Send return | Bytes/op | Allocs/op |
|---|---|---|---|
| Fake | 2.69 µs | 9,450 | 30 |
| Log | 3.88 µs | 10,192 | 34 |
| Mailgun | 48.05 µs | 27,414 | 223 |
| Resend | 52.99 µs | 33,298 | 113 |
| Postmark | 53.52 µs | 33,426 | 116 |
| SendGrid | 53.60 µs | 32,911 | 103 |
| SES | 103.72 µs | 106,222 | 610 |
| SMTP | 3.71 ms | 58,850 | 316 |
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.
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())
// 1MessageBuilder.Bcc
Bcc appends one blind-carbon-copy recipient.
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.
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.
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.
msg := mail.New(mailfake.New()).Message().
To("[email protected]", "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().
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.
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))
// 1Construction
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.
fake := mailfake.New()
mailer := mail.New(fake, mail.WithDefaultFrom("[email protected]", "Example"))
fmt.Println(mailer != nil)
// trueContent
MessageBuilder.Attach
Attach appends one in-memory attachment.
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.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().
From("[email protected]", "Example").
To("[email protected]", "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("[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.
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_123MessageBuilder.Metadata
Metadata sets one provider-facing metadata key/value pair.
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_123MessageBuilder.Subject
Subject sets the message subject.
msg := mail.New(mailfake.New()).Message().
To("[email protected]", "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("[email protected]", "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("[email protected]", "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("[email protected]", "Example"),
)
fmt.Println(mailer != nil)
// trueWithDefaultHeader
WithDefaultHeader configures a header applied when a message omits that header key.
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"])
// goforjWithDefaultMetadata
WithDefaultMetadata configures metadata applied when a message omits that metadata key.
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_123WithDefaultReplyTo
WithDefaultReplyTo configures the default reply-to recipients applied when a message omits them.
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.
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])
// transactionalDelivery
Mailer.Send
Send validates the message, applies defaults, and delegates delivery to the driver.
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)
// trueMessageBuilder.Build
Build applies defaults, validates, and returns the composed message without sending it.
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.
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())
// 1Logging
maillog.Driver.Send
Send validates the message and writes one JSON log record.
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\""))
// truemaillog.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.
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\""))
// 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: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
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: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
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: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
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)
// truemailmailgun.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.
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: "[email protected]", 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: "[email protected]", Name: "Example"},
To: []mail.Recipient{{Email: "[email protected]", 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: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
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)
// truemailpostmark.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.
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)
// 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)
// truemailresend.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.
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)
// 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: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(err == nil)
// falsemailsmtp.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.
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: "[email protected]",
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: "[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"))
// 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: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
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)
// truemailsendgrid.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.
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)
// 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: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
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{
From: &mail.Recipient{Email: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
Subject: "Welcome",
Text: "hello world",
})
fake.Reset()
fmt.Println(fake.SentCount())
// 0mailfake.Driver.Send
Send validates and records the message, returning the configured delivery error when set.
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())
// 1mailfake.Driver.SentCount
SentCount reports the number of recorded messages.
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())
// 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{
From: &mail.Recipient{Email: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
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: "[email protected]"},
To: []mail.Recipient{{Email: "[email protected]"}},
Subject: "Welcome",
Text: "hello world",
})
fmt.Println(fake.SentCount())
// 1Development
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.