package smtp2shoutrrr import ( "context" "errors" "fmt" "log/slog" "time" "github.com/emersion/go-smtp" ) // shutdownTimeout bounds how long in-flight sessions are given to finish once // the server is asked to stop. const shutdownTimeout = 10 * time.Second type Server struct { backend *smtp.Server } // Start accepts connections until ctx is cancelled or Stop is called, then // shuts the listener down gracefully. It returns nil on a clean shutdown. func (s *Server) Start(ctx context.Context) error { slog.Info("Started SMTP server", slog.String("addr", s.backend.Addr)) served := make(chan error, 1) go func() { served <- s.backend.ListenAndServe() }() select { case err := <-served: return err case <-ctx.Done(): } // The incoming context is already cancelled, so the shutdown deadline has // to be derived from a live one. shutdownCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), shutdownTimeout) defer cancel() if err := s.Stop(shutdownCtx); err != nil && !errors.Is(err, smtp.ErrServerClosed) { return err } return <-served } func (s *Server) Stop(ctx context.Context) error { slog.Info("Stopping SMTP server") return s.backend.Shutdown(ctx) } func NewSMTPServer(config *Config) *Server { be := &Backend{ config: config, } smtpBackend := smtp.NewServer(be) smtpBackend.Addr = fmt.Sprintf(":%d", config.Port) smtpBackend.WriteTimeout = 10 * time.Second smtpBackend.ReadTimeout = 10 * time.Second smtpBackend.MaxMessageBytes = 1024 * 1024 smtpBackend.MaxRecipients = 50 smtpBackend.AllowInsecureAuth = true return &Server{ backend: smtpBackend, } }