smtp2shoutrrr/server.go
Full-Stack Developer 8c12783e95
Some checks failed
CI / goreleaser-lint (pull_request) Failing after 37s
CI / format (pull_request) Successful in 1m20s
CI / test (pull_request) Successful in 2m36s
CI / lint (pull_request) Successful in 3m34s
CI / build (pull_request) Has been cancelled
deps: upgrade Go to 1.27.1, refresh dependencies and drop gotoolkit
Replaces the gotoolkit helpers with the standard library and the
libraries they wrapped:

- gotoolkit/encoding TOML wrapper -> pelletier/go-toml/v2 directly,
  behind a new smtp2shoutrrr.LoadConfig shared by both commands.
- gotoolkit/service + gotoolkit/model.Server -> signal.NotifyContext
  and a concrete *Server whose Start shuts the listener down
  gracefully when its context is cancelled.

Also drops the unused ANONYMOUS SASL client from cmd/sendmail, bumps
alpine, golangci-lint and the CI actions, and adds coverage for config
loading and the server lifecycle.

Closes FMG-2

Co-authored-by: multica-agent <github@multica.ai>
2026-09-07 16:28:11 +00:00

70 lines
1.6 KiB
Go

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,
}
}