diff --git a/smtp/smtp.go b/smtp/smtp.go new file mode 100644 index 0000000..d275d10 --- /dev/null +++ b/smtp/smtp.go @@ -0,0 +1,116 @@ +package smtp + +import ( + "crypto/tls" + "errors" + "fmt" + "net" + "net/mail" + "net/smtp" +) + +var ( + ErrSMTPInvalidConfig = errors.New("invalid smtp configuration") + ErrSMTPConnectionFailed = errors.New("connection to smtp server failed") + ErrSendMessageFailed = errors.New("could not send message") +) + +type SSLSMTPConfig struct { + URL string + Username string + Password string +} + +func (ssc *SSLSMTPConfig) Valid() bool { + if _, _, err := net.SplitHostPort(ssc.URL); err != nil { + return false + } + + return ssc.Username != "" && ssc.Password != "" +} + +type SSLSMTP struct { + config *SSLSMTPConfig + client *smtp.Client + connected bool +} + +func NewSSLSMTP(config *SSLSMTPConfig) *SSLSMTP { + return &SSLSMTP{ + config: config, + } +} + +func (s *SSLSMTP) Connect() error { + if !s.config.Valid() { + return ErrSMTPInvalidConfig + } + + host, _, _ := net.SplitHostPort(s.config.URL) + auth := smtp.PlainAuth("", s.config.Username, s.config.Password, host) + conn, err := tls.Dial("tcp", s.config.URL, &tls.Config{ServerName: host}) + if err != nil { + return fmt.Errorf("%w: %v", ErrSMTPConnectionFailed, err) + } + client, err := smtp.NewClient(conn, host) + if err != nil { + return fmt.Errorf("%w: %v", ErrSMTPConnectionFailed, err) + } + if err := client.Auth(auth); err != nil { + return fmt.Errorf("%w: %v", ErrSMTPConnectionFailed, err) + } + s.client = client + s.connected = true + + return nil +} + +func (s *SSLSMTP) Close() error { + if !s.connected { + return nil + } + + if err := s.client.Quit(); err != nil { + return fmt.Errorf("%w: %v", ErrSMTPConnectionFailed, err) + } + s.connected = false + + return nil +} + +func (s *SSLSMTP) Send(from, to mail.Address, subject, body string) error { + if err := s.Connect(); err != nil { + return err + } + defer s.Close() + + headers := make(map[string]string) + headers["From"] = from.String() + headers["To"] = to.String() + headers["Subject"] = subject + + message := "" + for k, v := range headers { + message += fmt.Sprintf("%s: %s\r\n", k, v) + } + message += fmt.Sprintf("\r\n%s", body) + + if err := s.client.Mail(from.Address); err != nil { + return fmt.Errorf("%w: %v", ErrSendMessageFailed, err) + } + if err := s.client.Rcpt(to.Address); err != nil { + return fmt.Errorf("%w: %v", ErrSendMessageFailed, err) + } + wc, err := s.client.Data() + if err != nil { + return fmt.Errorf("%w: %v", ErrSendMessageFailed, err) + } + if _, err := wc.Write([]byte(message)); err != nil { + return fmt.Errorf("%w: %v", ErrSendMessageFailed, err) + } + if err := wc.Close(); err != nil { + return fmt.Errorf("%w: %v", ErrSendMessageFailed, err) + } + + return nil +}