Skip to content

Send email

gas/email implements gas.EmailProvider on AWS SES. Bodies can be literal strings or rendered from templates held by a gas.TemplateProvider, which is the same interface Serve HTML uses, so both surfaces can share one store.

Terminal window
go get github.com/gasmod/gas/email
main.go
app := gas.NewApp(
gas.WithServiceInstance[gas.ConfigProvider](cfg),
gas.WithSingletonService[gas.Logger](gaslog.NewSlogLogger()),
// ses.New also asks for a gas.TemplateProvider, which is what
// SendFromTemplate renders through.
gas.WithServiceInstance[gas.TemplateProvider](templatememory.NewStore()),
gas.WithSingletonService[gas.EmailProvider](emailses.New()),
)

If your container resolves a concrete store type rather than the interface, use ses.NewWithCustomProvider[*mystore.Store]().

orders/service.go
type Service struct {
email gas.EmailProvider
}
func New(email gas.EmailProvider) *Service { return &Service{email: email} }
func (s *Service) confirmOrder(ctx context.Context, to string) error {
return s.email.Send(ctx, &gas.Email{
To: []string{to},
Subject: "Order confirmed",
HTMLBody: "<h1>Thank you</h1>",
TextBody: "Thank you",
ReplyTo: "support@example.com",
})
}

From defaults to Email.FromEmail from config and can be overridden per message. Cc, Bcc, ReplyTo, and arbitrary Headers are available.

// Templates are fetched from the TemplateProvider, parsed with html/template
// for HTML and text/template for the subject and text bodies. Any field left
// empty falls back to the matching field on the embedded Email.
func (s *Service) welcome(ctx context.Context, to, name string) error {
return s.email.SendFromTemplate(ctx, &gas.TemplatedEmail{
SubjectTemplate: "welcome-subject",
HTMLTemplate: "welcome-html",
TextTemplate: "welcome-text",
Data: map[string]string{"Name": name},
Email: gas.Email{To: []string{to}},
})
}

Subject and text bodies render with text/template; the HTML body renders with html/template, so it escapes by default. Any template field left empty falls back to the corresponding literal field on the embedded Email, which means you can template the body and hard-code the subject.

Point Email.Endpoint at LocalStack and nothing else changes:

Terminal window
EMAIL__REGION=us-east-1
EMAIL__FROM_EMAIL=noreply@example.com
EMAIL__ENDPOINT=http://localhost:4566
Field Description
Email.Region AWS region (required)
Email.FromEmail Default sender address (required)
Email.Endpoint Custom endpoint, for LocalStack. Empty uses AWS
Email.AccessKeyID Static access key. Empty uses the default credential chain
Email.SecretAccessKey Static secret key

The service implements gas.ReadyReporter, returning email.ErrClosed once Close has run so a Kubernetes readiness probe depools the pod during a drain. gas.HealthReporter is deliberately not implemented: the SES client is stateless, so there is no broken state a restart would clear.

emailtest.MockEmail records every call and delegates to SendFn and SendFromTemplateFn when set, so a signup flow can assert that exactly one welcome email was sent without touching AWS. See Test a service.