Documentation
¶
Overview ¶
Package mail sends what an application has to say to somebody.
The shape is Laravel's, because a developer coming from there should recognise it: a Mailable declares an Envelope and a Content, a Mailer sends it, and the transport behind the Mailer is configuration rather than a decision the calling code makes.
type WelcomeEmail struct{ Name, Link string }
func (m WelcomeEmail) Envelope() mail.Envelope {
return mail.Envelope{Subject: "Welcome"}
}
func (m WelcomeEmail) Content() mail.Content {
return mail.Content{View: "mail.welcome", Data: m}
}
mailer.To("you@example.com").Send(ctx, WelcomeEmail{Name: "Ada"})
What is deliberately absent ¶
No facade, no `Mail::` global, and no queue by default. Laravel queues a mailable when it implements ShouldQueue, which is an interface the class opts into at a distance; here sending on the queue is `jobs.Dispatch` with a job that sends, and the difference is visible at the call site.
No markdown mailables. Laravel has them because Blade cannot easily produce both an HTML and a text part; kyse produces whatever the view produces, and a second templating language for e-mail is the second way to draw a page that RULE 9 refuses.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoRecipient = errors.New("mail: no recipient")
ErrNoRecipient is returned by Send when nobody was addressed. It is an error rather than a silent no-op: a message with no recipient is a message somebody meant to send.
Functions ¶
func Render ¶
Render turns a message into the bytes an SMTP server receives.
It is exported because a transport that speaks a provider's HTTP API does not need it and a transport that speaks SMTP does, and both live outside this package once the adapters exist.
What it gets right that a Sprintf does not ¶
A header carrying a non-ASCII subject has to be encoded, or the client shows mojibake -- and "Você tem uma fatura" is the first subject anybody writes here. mime.QEncoding does it, and does nothing when the text is plain ASCII.
A body line longer than 998 bytes is refused by the protocol, and an HTML document is one long line often enough. quoted-printable folds it.
A line consisting of a single dot ends the message: a body containing one is a body that is silently truncated, and the transfer encoding removes that too.
Types ¶
type Address ¶
type Address struct {
// Email is the address itself, and the only required half.
Email string
// Name is what a client shows instead of the address. Empty is fine.
Name string
}
Address is one mailbox, with the display name that goes in front of it.
type Array ¶
type Array struct {
// contains filtered or unexported fields
}
Array keeps what was sent, for a test to read.
It is safe for concurrent use, because a test that sends from two goroutines and reads from a third is a test that would otherwise fail under -race for a reason that has nothing to do with what it is proving.
func (*Array) Reset ¶
func (a *Array) Reset()
Reset forgets everything. A test that shares a transport between cases calls it, and one that does not share it does not need to.
type Content ¶
type Content struct {
// View is the HTML part, by the name the view is registered under.
View string
// TextView is the plain-text part, also by view name.
//
// It exists because Text alone did not do the job it was written for. A
// project generated a mail/password-reset-text view, and nothing could ever
// send it: the only way in was a Go string literal, so the view sat in the
// tree looking wired while every message went out HTML-only. Found by audit.
//
// A message with no text part is filed as spam more often, and every client
// that cannot render HTML shows nothing at all.
TextView string
// Text is the plain-text part as a literal, for a message short enough that
// a view would be ceremony. TextView wins when both are set.
Text string
// Data is what both parts render from.
Data any
}
Content is what the body is made of.
A view name and its data, rather than a string: the message is drawn by the same view layer as a page, so a field that does not exist is a compile error and interpolation is escaped by construction.
type Envelope ¶
type Envelope struct {
From Address
To []Address
CC []Address
BCC []Address
ReplyTo []Address
Subject string
// Tags and Metadata are carried by the transports that support them and
// dropped by the ones that do not. They are how a provider's dashboard
// groups "password resets" apart from "invoices".
Tags []string
Metadata map[string]string
}
Envelope is who a message is from, who it is to, and what it says it is.
type ErrRetryable ¶ added in v0.19.0
type ErrRetryable struct {
// contains filtered or unexported fields
}
ErrRetryable marks a failure worth trying again.
A 429 or a 5xx from a provider is not the same event as a rejected address, and treating them alike is how a verification e-mail is silently lost during a rate limit. A job that sends checks for this and reschedules; a request that sends inline reports it and moves on.
func (ErrRetryable) Error ¶ added in v0.19.0
func (e ErrRetryable) Error() string
func (ErrRetryable) Unwrap ¶ added in v0.19.0
func (e ErrRetryable) Unwrap() error
type Log ¶
type Log struct{}
Log writes the message to the log instead of sending it.
It is the development default, and what makes `aru dev` work with nothing installed. The whole body is logged, because the reason to read it is to follow the link inside.
type Mailer ¶
type Mailer struct {
// contains filtered or unexported fields
}
Mailer sends a Mailable through a Transport.
func (*Mailer) To ¶
To starts a message to one or more addresses.
mailer.To("you@example.com").Send(ctx, WelcomeEmail{})
It returns a pending message rather than sending, so cc and bcc chain in the order they are read.
type Message ¶
Message is what a Transport receives: an envelope and the two rendered parts.
The transport never sees a view name or a Mailable. Rendering happens once, in the Mailer, so a transport cannot render differently from another one.
type Pending ¶
type Pending struct {
// contains filtered or unexported fields
}
Pending is a message being addressed.
func (*Pending) Send ¶
Send renders the mailable and hands it to the transport.
It is synchronous. Sending on the queue is a job that calls this, and that is deliberate: a call that sometimes blocks for two seconds and sometimes does not, decided by an interface the mailable implements somewhere else, is a call nobody can reason about from the line they are reading.
type Renderer ¶
Renderer draws the view a Content names.
An interface here rather than the view package directly, because mail is imported by the modules that send and importing the view package from all of them would put the whole view registry behind every one. framework/view satisfies it.
type Resend ¶ added in v0.19.0
type Resend struct {
// Key is the API key, `re_...`. It comes from the environment and never from
// a literal -- a key in source is a key in every clone of the repository.
Key string
// Endpoint overrides the API, for a test. Empty is resend.com.
Endpoint string
// Timeout bounds the request. Without one a hung provider holds the request
// that triggered it for as long as the provider likes.
Timeout time.Duration
// Client is the HTTP client. Empty builds one with Timeout.
Client *http.Client
}
Resend sends through resend.com.
It is the default recommendation for an application that has outgrown the log transport: a domain, a DNS record and an API key, and no server to run.
type SMTP ¶
type SMTP struct {
// Host and Port are the server. 587 is submission with STARTTLS, which is
// what a provider gives you; 25 is server-to-server and is usually blocked.
Host string
Port string
// Username and Password authenticate. Both empty sends unauthenticated,
// which is right for a local relay and wrong for anything reachable.
Username string
Password string
// Timeout bounds the whole exchange. Without one a hung server holds the
// request that triggered it until the client gives up -- and net/smtp has no
// deadline of its own.
Timeout time.Duration
}
SMTP sends over SMTP, with STARTTLS.
type SendGrid ¶ added in v0.19.0
type SendGrid struct {
// Key is the API key, `SG....`.
Key string
// Endpoint overrides the API, for a test. Empty is sendgrid.com.
Endpoint string
// Timeout bounds the request.
Timeout time.Duration
// Client is the HTTP client. Empty builds one with Timeout.
Client *http.Client
}
SendGrid sends through sendgrid.com.
The second provider rather than the only one, because a transport with one implementation is an interface nobody has proved is an interface.
type Transport ¶
type Transport interface {
Send(ctx context.Context, m Message) error
// Name is what appears in a log line and on the debug console.
Name() string
}
Transport delivers a rendered message.
One method, so writing one is small: an adapter for a provider is a POST and an error, and everything above it -- addressing, rendering, validation -- has already happened.