package main import ( "errors" "fmt" "log/slog" "net/smtp" "os" "git.nakama.town/fmartingr/smtp2shoutrrr" ) // The PLAIN mechanism name. const Plain = "PLAIN" type plainClient struct { Identity string Username string Password string } func (a *plainClient) Start(si *smtp.ServerInfo) (mech string, ir []byte, err error) { mech = Plain ir = []byte(a.Identity + "\x00" + a.Username + "\x00" + a.Password) return } func (a *plainClient) Next(challenge []byte, b bool) (response []byte, err error) { slog.Debug("SASL challenge received", slog.String("challenge", string(challenge))) return nil, nil } // NewPlainClient is a client implementation of the PLAIN authentication // mechanism, as described in RFC 4616. Unlike smtp.PlainAuth it does not // require a TLS connection, which the development server does not offer. // Authorization identity may be left blank to indicate that it is the same as // the username. func NewPlainClient(identity, username, password string) smtp.Auth { return &plainClient{identity, username, password} } const configPath = "config.toml" func main() { if err := run(); err != nil { slog.Error("sendmail failed", slog.String("err", err.Error())) os.Exit(1) } } func run() error { config, err := smtp2shoutrrr.LoadConfig(configPath) if err != nil { return fmt.Errorf("loading configuration: %w", err) } // The development server listens on localhost without TLS. hostname := "localhost" auth := NewPlainClient("", config.Username, config.Password) slog.Info("Using first recipient configuration to send a test email") if len(config.Recipients) == 0 { return errors.New("no recipients found in configuration") } if len(config.Recipients[0].Addresses) == 0 { return errors.New("no email addresses found in first recipient configuration") } recipients := []string{config.Recipients[0].Addresses[0]} msg := []byte("Subject: Test notification\r\n\r\nThis is a test notification") from := "hello@localhost" return smtp.SendMail(fmt.Sprintf("%s:%d", hostname, config.Port), auth, from, recipients, msg) }