App
Mailers
Render mailer templates and keep message delivery behind a service boundary.
App-owned service
Mail is an application service. GoLazy provides rendering and delivery boundaries, but the app decides which mailers exist, how delivery is configured, and which controllers or jobs call them.
Wire the mailer in init/dependencies.go, then expose application operations
through typed context helpers just like any other dependency.
Mailer setup
golazy.dev/lazymailer renders mail through the same view renderer used by
controllers. Delivery is a separate interface, so SMTP, memory inboxes, and
provider-specific transports can be swapped without changing templates.
func Dependencies(deps *lazydeps.Scope) error {
_, err := lazydeps.Service(deps, "mailer", func(ctx context.Context) (
context.Context,
*lazymailer.Mailer,
error,
context.CancelFunc,
) {
registry := lazymailer.NewRegistry("default", map[string]lazymailer.Delivery{
"default": lazymailer.SMTPDelivery{
Addr: os.Getenv("SMTP_ADDR"),
Auth: smtp.PlainAuth(
"",
os.Getenv("SMTP_USER"),
os.Getenv("SMTP_PASSWORD"),
os.Getenv("SMTP_HOST"),
),
},
})
mailer, err := lazymailer.New(ctx, registry)
if err != nil {
return ctx, nil, fmt.Errorf("initialize mailer: %w", err), nil
}
return lazymailer.WithContext(ctx, mailer), mailer, nil, nil
})
return err
}
Use lazymailer.MemoryDelivery in tests or development when you want to assert
which messages would be sent.
Application mailer types
Put mailer types under app/mailers. A concrete mailer embeds or stores a
lazymailer.Base, sets template variables, and calls Mail.
package notices
type NoticeMailer struct {
base lazymailer.Base
}
func New(ctx context.Context) (*NoticeMailer, error) {
base, err := lazymailer.NewBase(ctx, "notice_mailer", lazymailer.Defaults{
From: lazymailer.MustParseAddress("GoLazy <[email protected]>"),
Delivery: "default",
Layout: "mailer",
})
if err != nil {
return nil, err
}
return &NoticeMailer{base: base}, nil
}
func (m *NoticeMailer) Welcome(to lazymailer.Address, name string) error {
m.base.Set("name", name)
return m.base.Mail(lazymailer.Options{
Action: "welcome",
To: []lazymailer.Address{to},
Subject: "Welcome",
})
}
The controller should call an app operation such as Welcome; it should not
build MIME messages or know which delivery transport is active.
Mailer views
Mailer templates live next to normal views. The controller name passed to
NewBase selects the view directory, and the action selects the template name.
app/views/layouts/mailer.text.tpl
app/views/layouts/mailer.html.tpl
app/views/notice_mailer/welcome.text.tpl
app/views/notice_mailer/welcome.html.tpl
When both text and HTML templates exist, GoLazy builds a multipart alternative message. If only one format exists, the message uses that body.
Related media services
Stored files, application assets, object storage, and generated media variants now live in the Media guide. Keep mail delivery and media storage as separate app services unless your product operation genuinely needs both, such as sending an email with a generated preview link.