package postcard import ( "context" "fmt" ) // Message is the Send contract for plugin callers. type Message struct { Template string To []string Cc []string Bcc []string ReplyTo string Vars map[string]any Subject string } // Mailer sends a registered template through the configured driver. type Mailer interface { Send(ctx context.Context, msg Message) error } // Options configure an app-scoped mailer. type Options struct { From string CSS string BrandCSS string } type mailer struct { catalog *Catalog driver Driver opts Options } // NewMailer returns a mailer that renders from cat and delivers through driver. func NewMailer(cat *Catalog, driver Driver, opts Options) Mailer { if cat == nil { cat = NewCatalog() } return &mailer{catalog: cat, driver: driver, opts: opts} } // Send renders msg.Template and delivers it. It performs no locale selection; // the caller must pass the full dotted name, including any -en suffix. func (m *mailer) Send(ctx context.Context, msg Message) error { if m == nil { return fmt.Errorf("postcard: mailer is nil") } if m.driver == nil { return fmt.Errorf("postcard: driver is nil") } if msg.Template == "" { return fmt.Errorf("postcard: template is empty") } if len(msg.To) == 0 && len(msg.Cc) == 0 && len(msg.Bcc) == 0 { return fmt.Errorf("postcard: no recipients") } rendered, err := m.catalog.render(msg, renderOptions{ css: m.opts.CSS, brandCSS: m.opts.BrandCSS, }) if err != nil { return err } rendered.From = m.opts.From if err := m.driver.Send(ctx, rendered); err != nil { return fmt.Errorf("postcard: %w", err) } return nil }