// Package email sends QSO recordings to correspondents via SMTP. Pure Go (no // CGO) using go-mail; supports implicit SSL (465), STARTTLS (587) or none. package email import ( "fmt" "time" "github.com/wneessen/go-mail" ) // Config is the user's SMTP configuration. type Config struct { Host string Port int User string Password string From string Encryption string // "ssl" | "starttls" | "none" Auth bool // SMTP requires authorization (send username/password) } func (c Config) opts() []mail.Option { o := []mail.Option{mail.WithPort(c.Port), mail.WithTimeout(30 * time.Second)} if c.Auth && c.User != "" { // AutoDiscover negotiates whatever mechanism the server advertises // (LOGIN, PLAIN, CRAM-MD5, …). OVH, for instance, rejects forced PLAIN. o = append(o, mail.WithSMTPAuth(mail.SMTPAuthAutoDiscover), mail.WithUsername(c.User), mail.WithPassword(c.Password)) } switch c.Encryption { case "ssl": o = append(o, mail.WithSSL()) case "none": o = append(o, mail.WithTLSPolicy(mail.NoTLS)) default: // starttls o = append(o, mail.WithTLSPolicy(mail.TLSMandatory)) } return o } // Send delivers a plain-text email to `to`, optionally attaching a file. func Send(cfg Config, to, subject, body, attachPath string) error { if cfg.Host == "" { return fmt.Errorf("SMTP server not configured") } if to == "" { return fmt.Errorf("no recipient e-mail") } from := cfg.From if from == "" { from = cfg.User } m := mail.NewMsg() if err := m.From(from); err != nil { return fmt.Errorf("bad sender %q: %w", from, err) } if err := m.To(to); err != nil { return fmt.Errorf("bad recipient %q: %w", to, err) } m.Subject(subject) m.SetBodyString(mail.TypeTextPlain, body) if attachPath != "" { m.AttachFile(attachPath) } client, err := mail.NewClient(cfg.Host, cfg.opts()...) if err != nil { return fmt.Errorf("smtp client: %w", err) } if err := client.DialAndSend(m); err != nil { return fmt.Errorf("send: %w", err) } return nil }