Documentation
¶
Overview ¶
Package actionmailer is a pure-Go (CGO-free) reimplementation of the foundation of Ruby on Rails' Action Mailer — the framework that composes and delivers email from mailer classes. It is faithful to MRI / Rails 4.0.5 semantics for message composition, multipart MIME assembly, delivery methods, and the interceptor/observer hooks, without any Ruby runtime.
What it is — and isn't ¶
Composing a message (assembling headers, multipart/alternative bodies, attachments, and choosing a delivery method) is deterministic and interpreter-independent, so it lives here as pure Go. Two things are host seams rather than baked in:
- Rendering a mail body from a template is delegated to a RenderBody callback. In Rails this is Action View resolving `welcome.text.erb` / `welcome.html.erb`; here the host (for example go-embedded-ruby wiring up go-ruby-actionview) supplies the rendered text/HTML for each format and this package assembles the MIME structure around it.
- The on-the-wire byte encoding of a message is delegated to go-ruby-mail (the Mail gem): this package builds a *mail.Message and lets go-ruby-mail fold headers, encode words, and lay out boundaries.
API shape ¶
New creates a Base — the analogue of a subclass of ActionMailer::Base — carrying class-level configuration (Base.Default params, the delivery method, perform/raise flags, interceptors, observers). Base.Register binds a named action to an Action closure; running it with Base.Process returns a MessageDelivery (the analogue of `MyMailer.welcome(user)`), whose MessageDelivery.DeliverNow / MessageDelivery.DeliverLater / MessageDelivery.Message mirror the Rails proxy.
Inside an action, the *Mailer receiver exposes Mailer.Mail (the `mail(to:, from:, subject:, …)` call), Mailer.Headers, and Mailer.Attachments (both regular and inline/content-id attachments).
Delivery methods ¶
DeliveryMethod is the pluggable transport. TestDelivery appends to a slice (ActionMailer::Base.deliveries), FileDelivery writes one file per recipient, and SMTPDelivery speaks SMTP via net/smtp through an injectable send function so tests never open a socket.
Index ¶
- Variables
- type Action
- type Attachment
- type Attachments
- func (a *Attachments) All() []*Attachment
- func (a *Attachments) Get(name string) *Attachment
- func (a *Attachments) Inline() []*Attachment
- func (a *Attachments) Regular() []*Attachment
- func (a *Attachments) Set(name string, data []byte) *Attachment
- func (a *Attachments) SetInline(name string, data []byte) *Attachment
- type Base
- func (b *Base) Default(key, value string) *Base
- func (b *Base) Process(action string, params ...any) *MessageDelivery
- func (b *Base) Register(action string, fn Action) *Base
- func (b *Base) RegisterInterceptor(i Interceptor) *Base
- func (b *Base) RegisterObserver(o Observer) *Base
- func (b *Base) UseSendmail() *Base
- func (b *Base) UseTestDelivery() *Base
- func (b *Base) UseView(v *View) *Base
- type DeliveryMethod
- type EvalTemplate
- type FileDelivery
- type I18n
- type Interceptor
- type MailOptions
- type Mailer
- type MessageDelivery
- type Observer
- type RenderBody
- type SMTPDelivery
- type SMTPFunc
- type SendmailDelivery
- type TemplateStore
- func (s *TemplateStore) Add(mailer, action, format, source string) *TemplateStore
- func (s *TemplateStore) AddLayout(name, format, locale, source string) *TemplateStore
- func (s *TemplateStore) AddLocalized(mailer, action, format, locale, source string) *TemplateStore
- func (s *TemplateStore) AddPartial(name, format, locale, source string) *TemplateStore
- type TestDelivery
- type View
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNoTemplate is returned by a [RenderBody] seam to indicate that no // template exists for the requested format, so that format is skipped. ErrNoTemplate = errors.New("actionmailer: no template for format") // ErrNoRenderBody is returned by [Mailer.Mail] when a body must be rendered // but no [RenderBody] seam is configured on the [Base]. ErrNoRenderBody = errors.New("actionmailer: no RenderBody seam configured") // ErrNoDeliveryMethod is returned when a message must be delivered but no // [DeliveryMethod] is configured. ErrNoDeliveryMethod = errors.New("actionmailer: no delivery method configured") )
Sentinel errors returned during composition.
var ErrNoEval = errors.New("actionmailer: no EvalTemplate seam configured")
ErrNoEval is returned by a View when it must evaluate a template but no EvalTemplate seam is configured.
var GenerateBoundary = func(seq int) string { return fmt.Sprintf("boundary_%d", seq) }
GenerateBoundary produces a MIME multipart boundary for the seq-th container of a message. The default is deterministic (so composed output is reproducible in tests); a host may override it for random boundaries.
var GenerateContentID = func(name string) string { return "<" + name + ">" }
GenerateContentID produces the Content-ID header value for an inline attachment. The default derives it deterministically from the name; a host may override it (Rails uses a random token @ the host name).
Functions ¶
This section is empty.
Types ¶
type Action ¶
Action is a mailer action: the body of a Rails mailer method. It receives the per-invocation *Mailer (the mailer instance / `self`) and the caller's parameters, and is expected to call Mailer.Mail to compose the message.
type Attachment ¶
type Attachment struct {
// Name is the filename.
Name string
// Data is the decoded content (base64-encoded on the wire).
Data []byte
// ContentType is the media type; defaulted from the filename extension and
// overridable before composition.
ContentType string
// Inline reports whether this is an inline (Content-Disposition: inline)
// attachment carrying a Content-ID.
Inline bool
// ContentID is the Content-ID for an inline attachment (set on SetInline).
ContentID string
}
Attachment is a single attachment, mirroring a Mail::Part built from `attachments[name] = data`.
type Attachments ¶
type Attachments struct {
// contains filtered or unexported fields
}
Attachments is the ordered attachment collection of a mailer action, mirroring the `attachments` proxy.
func (*Attachments) All ¶
func (a *Attachments) All() []*Attachment
All returns every attachment in insertion order.
func (*Attachments) Get ¶
func (a *Attachments) Get(name string) *Attachment
Get returns the attachment named name, or nil, mirroring `attachments[name]`.
func (*Attachments) Inline ¶
func (a *Attachments) Inline() []*Attachment
Inline returns the inline attachments in insertion order.
func (*Attachments) Regular ¶
func (a *Attachments) Regular() []*Attachment
Regular returns the non-inline attachments in insertion order.
func (*Attachments) Set ¶
func (a *Attachments) Set(name string, data []byte) *Attachment
Set adds (or replaces) a regular attachment named name and returns it, mirroring `attachments[name] = data`.
func (*Attachments) SetInline ¶
func (a *Attachments) SetInline(name string, data []byte) *Attachment
SetInline adds (or replaces) an inline attachment and returns it, assigning a Content-ID, mirroring `attachments.inline[name] = data`.
type Base ¶
type Base struct {
// Name is the mailer class name (e.g. "UserMailer"), passed to the
// [RenderBody] seam so it can locate templates.
Name string
// Defaults holds the default headers/params (the Rails `default from: …`).
// Recognised keys "from", "to", "cc", "bcc", "reply_to", "subject" and
// "content_type" fill the corresponding message fields when an action omits
// them; any other key becomes a default header.
Defaults map[string]string
// RenderBody is the body-rendering seam (nil unless bodies are rendered).
RenderBody RenderBody
// DeliveryMethod is the transport used by DeliverNow/DeliverLater.
DeliveryMethod DeliveryMethod
// PerformDeliveries gates whether the delivery method is actually invoked
// (ActionMailer::Base.perform_deliveries). Defaults to true.
PerformDeliveries bool
// RaiseDeliveryErrors reports whether a delivery-method error propagates
// (ActionMailer::Base.raise_delivery_errors). Defaults to true.
RaiseDeliveryErrors bool
// Deliveries is the sink used by [TestDelivery] (ActionMailer::Base.deliveries).
Deliveries []*mail.Message
// Now supplies the Date header when an action does not set one. When nil,
// no Date is added. Defaults to time.Now.
Now func() time.Time
// MessageIDGen, when non-nil, supplies the Message-ID header.
MessageIDGen func() string
// EnqueueJob is the Active Job seam used by [MessageDelivery.DeliverLater].
// When nil, DeliverLater runs the delivery inline.
EnqueueJob func(job func() error) error
// I18n resolves a subject when an action omits one (and no "subject" default
// is set), mirroring ActionMailer's default_i18n_subject. When nil, an
// action with no subject simply has no Subject header.
I18n *I18n
// contains filtered or unexported fields
}
Base is the analogue of a subclass of ActionMailer::Base: it holds the class-level configuration shared by every delivery, and the registry of named actions. Construct one with New.
func New ¶
New creates a Base named name with Rails-default flags (perform_deliveries and raise_delivery_errors both true) and time.Now as the Date source.
func (*Base) Default ¶
Default sets a default param/header (Rails `default key: value`) and returns the receiver for chaining.
func (*Base) Process ¶
func (b *Base) Process(action string, params ...any) *MessageDelivery
Process runs the named action to compose a message and returns a MessageDelivery, mirroring `MyMailer.action(params)`. Composition errors (unknown action, action error, render error, or an action that never called Mailer.Mail) are captured on the delivery and surface from its methods.
func (*Base) Register ¶
Register binds an Action to a name so it can be run with Base.Process.
func (*Base) RegisterInterceptor ¶
func (b *Base) RegisterInterceptor(i Interceptor) *Base
RegisterInterceptor adds a delivery interceptor.
func (*Base) RegisterObserver ¶
RegisterObserver adds a delivery observer.
func (*Base) UseSendmail ¶
UseSendmail wires the delivery method to a SendmailDelivery, mirroring `config.action_mailer.delivery_method = :sendmail`.
func (*Base) UseTestDelivery ¶
UseTestDelivery wires the delivery method to a TestDelivery appending to b.Deliveries, mirroring `config.action_mailer.delivery_method = :test`.
type DeliveryMethod ¶
DeliveryMethod is the pluggable transport that actually delivers a composed message, mirroring ActionMailer's delivery_method registry.
type EvalTemplate ¶
EvalTemplate is the template-evaluation seam: it compiles+runs a template source with the given locals and returns the rendered output. In Rails this is Action View handing the .erb to Erubis and running it; here a host wires it to go-ruby-erb + a Ruby runtime (rbgo), keeping this package CGO- and runtime-free. The View owns everything around it — template lookup, locale fallback, implicit text/html pairing, and layout wrapping.
type FileDelivery ¶
type FileDelivery struct {
// Location is the destination directory.
Location string
// contains filtered or unexported fields
}
FileDelivery is the :file delivery method: it writes one file per recipient into a directory, each containing the encoded message.
func NewFileDelivery ¶
func NewFileDelivery(location string) *FileDelivery
NewFileDelivery returns a FileDelivery writing into location.
type I18n ¶
type I18n struct {
// Locale is the active locale (default "en" when empty).
Locale string
// Translations maps a full dotted key to its (possibly interpolated) value.
Translations map[string]string
}
I18n is the subject-translation store consulted when a mailer action calls mail(...) without a subject (and no "subject" default is set), mirroring ActionMailer's default_i18n_subject. Translations are keyed "<locale>.<mailer_scope>.<action>.subject" — for example "en.user_mailer.welcome.subject" — where the mailer scope is the mailer class name underscored. Values may embed %{name} interpolations resolved from the MailOptions.SubjectVars.
When no translation is found the humanised action name is used (the Rails default: "welcome" -> "Welcome").
type Interceptor ¶
Interceptor is registered with Base.RegisterInterceptor and is invoked with the fully composed message just before delivery, mirroring register_interceptor / Mail's inform_interceptors.
type MailOptions ¶
type MailOptions struct {
// From is the sender; when blank the "from" default is used.
From string
// To, Cc, Bcc and ReplyTo are recipient/reply lists; each list is joined
// with ", " into its header. Blank lists fall back to the matching default.
To, Cc, Bcc, ReplyTo []string
// Subject is the subject; when blank the "subject" default is used, and if
// that is blank too an i18n lookup (see [Base.I18n]) resolves it.
Subject string
// SubjectVars are the %{name} interpolation values for an i18n subject
// lookup (the interpolations of ActionMailer's default_i18n_subject).
SubjectVars map[string]any
// Date sets the Date header when HasDate is true; otherwise Base.Now is used.
Date time.Time
HasDate bool
// Headers are extra headers set on the message (highest precedence).
Headers map[string]string
// Body, when non-empty, is used as a single-part body of ContentType
// (default text/plain), bypassing the RenderBody seam.
Body string
// ContentType is the media type for an explicit Body (default text/plain).
ContentType string
// Formats lists the formats rendered via the RenderBody seam when Body is
// empty (default {"text", "html"}).
Formats []string
// Locals are passed through to the RenderBody seam.
Locals map[string]any
}
MailOptions are the keyword arguments of the Rails `mail(...)` call.
type Mailer ¶
type Mailer struct {
// contains filtered or unexported fields
}
Mailer is the per-invocation mailer instance — the `self` of a Rails mailer action. It accumulates attachments and extra headers and, via Mailer.Mail, composes the message. Actions receive it from Base.Process.
func (*Mailer) Attachments ¶
func (m *Mailer) Attachments() *Attachments
Attachments returns the attachment collection for this action, mirroring the mailer's `attachments` accessor.
func (*Mailer) Headers ¶
Headers merges the given headers into the message being built, mirroring the mailer's `headers(hash)` call. Later calls override earlier ones.
func (*Mailer) Mail ¶
func (m *Mailer) Mail(opts MailOptions) error
Mail composes the message from opts and the accumulated attachments/headers, mirroring `mail(...)`. It stores the result so Base.Process can return it.
type MessageDelivery ¶
type MessageDelivery struct {
// contains filtered or unexported fields
}
MessageDelivery is the lazy delivery proxy returned by Base.Process, mirroring ActionMailer::MessageDelivery (`MyMailer.welcome(user)`). It carries the composed message, or the composition error, and exposes the deliver verbs.
func (*MessageDelivery) DeliverLater ¶
func (d *MessageDelivery) DeliverLater() error
DeliverLater enqueues the delivery through the Active Job seam (Base.EnqueueJob), mirroring `.deliver_later`. When no seam is configured the delivery runs inline. Composition errors surface here.
func (*MessageDelivery) DeliverNow ¶
func (d *MessageDelivery) DeliverNow() error
DeliverNow delivers the message immediately through the configured delivery method, mirroring `.deliver_now`. Composition errors surface here.
type Observer ¶
Observer is registered with Base.RegisterObserver and is invoked with the message just after delivery, mirroring register_observer / inform_observers.
type RenderBody ¶
RenderBody is the body-rendering seam. Given the mailer name, the action, a format ("text" / "html" / …) and the action's locals, it returns the rendered body for that format. Returning ErrNoTemplate signals that no template exists for the format, so it is skipped (mirroring Action View not finding a `.text` or `.html` template); any other error aborts composition.
type SMTPDelivery ¶
type SMTPDelivery struct {
// Addr is the "host:port" of the SMTP server.
Addr string
// Host is the server host used for PLAIN auth; when empty it is derived
// from Addr.
Host string
// Username and Password enable PLAIN auth when Username is non-empty.
Username string
Password string
// Send performs the send; defaults to net/smtp.SendMail.
Send SMTPFunc
}
SMTPDelivery is the :smtp delivery method. The actual send is performed by Send (net/smtp.SendMail by default), which is injectable for testing.
func NewSMTPDelivery ¶
func NewSMTPDelivery(addr string) *SMTPDelivery
NewSMTPDelivery returns an SMTPDelivery targeting addr ("host:port") using net/smtp.SendMail.
type SMTPFunc ¶
SMTPFunc is the socket seam used by SMTPDelivery: it matches the signature of net/smtp.SendMail so tests can inject a fake and never open a socket.
type SendmailDelivery ¶
type SendmailDelivery struct {
// Location is the sendmail binary path (default "/usr/sbin/sendmail").
Location string
// Arguments are the flags passed to sendmail (default {"-i", "-t"}).
Arguments []string
// contains filtered or unexported fields
}
SendmailDelivery is the :sendmail delivery method: it pipes the encoded message to a local sendmail-compatible binary, mirroring ActionMailer's :sendmail. The default binary is /usr/sbin/sendmail invoked with "-i -t" (read recipients from the message, don't treat a lone dot as end-of-input).
func NewSendmailDelivery ¶
func NewSendmailDelivery() *SendmailDelivery
NewSendmailDelivery returns a SendmailDelivery using /usr/sbin/sendmail -i -t.
func (*SendmailDelivery) Deliver ¶
func (s *SendmailDelivery) Deliver(m *mail.Message) error
Deliver pipes the encoded message to the sendmail binary. When the arguments do not already carry -t, the To/Cc/Bcc recipients are appended so sendmail knows where to route the message (matching the gem, which appends the destinations when it is not reading them from the headers).
type TemplateStore ¶
type TemplateStore struct {
// contains filtered or unexported fields
}
TemplateStore holds mailer template, layout and partial sources, keyed for lookup with locale fallback — the in-memory analogue of Action View's resolver for the mail case. Register sources with TemplateStore.Add, TemplateStore.AddLocalized, TemplateStore.AddLayout and TemplateStore.AddPartial; lookups first try the requested locale, then the locale-less entry.
func NewTemplateStore ¶
func NewTemplateStore() *TemplateStore
NewTemplateStore returns an empty TemplateStore.
func (*TemplateStore) Add ¶
func (s *TemplateStore) Add(mailer, action, format, source string) *TemplateStore
Add registers a locale-less template for a mailer action + format (welcome.text.erb), returning the store for chaining.
func (*TemplateStore) AddLayout ¶
func (s *TemplateStore) AddLayout(name, format, locale, source string) *TemplateStore
AddLayout registers a layout for a base name + format (layouts/mailer.html.erb), with an optional locale ("" for the fallback).
func (*TemplateStore) AddLocalized ¶
func (s *TemplateStore) AddLocalized(mailer, action, format, locale, source string) *TemplateStore
AddLocalized registers a template for a specific locale (welcome.en.text.erb). A locale of "" is the fallback variant.
func (*TemplateStore) AddPartial ¶
func (s *TemplateStore) AddPartial(name, format, locale, source string) *TemplateStore
AddPartial registers a partial by name + format (_footer.html.erb), with an optional locale ("" for the fallback).
type TestDelivery ¶
type TestDelivery struct {
// contains filtered or unexported fields
}
TestDelivery is the :test delivery method: it appends each delivered message to a slice (ActionMailer::Base.deliveries) instead of sending it.
func NewTestDelivery ¶
func NewTestDelivery(dst *[]*mail.Message) *TestDelivery
NewTestDelivery returns a TestDelivery appending to dst.
type View ¶
type View struct {
// Store holds the template, layout and partial sources.
Store *TemplateStore
// Eval evaluates a resolved template source. Required.
Eval EvalTemplate
// Locale is the active locale for lookups (default "en" when empty).
Locale string
// Layout is the default layout base name (e.g. "mailer"); "" renders bodies
// without a layout.
Layout string
// Context is an optional go-ruby-actionview context supplying the view
// helpers/routing available inside templates. It is copied per render so its
// RenderTemplate seam can be wired to the store; a nil Context uses a zero one.
Context *actionview.Context
}
View resolves and renders mailer templates and layouts (with locale fallback and implicit text/html pairing) through a go-ruby-actionview actionview.Context and an EvalTemplate seam, producing a RenderBody to wire to Base.RenderBody.
func (*View) RenderBody ¶
func (v *View) RenderBody() RenderBody
RenderBody returns a RenderBody closure over the view: for each requested format it resolves the mailer's template (skipping the format with ErrNoTemplate when none exists), evaluates it through the actionview Context, and wraps the result in the format's layout (exposing the rendered body to the layout as the html-safe "yield" local) when a layout is configured.
