141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
package smtp2shoutrrr
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/mail"
|
|
"net/url"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/containrrr/shoutrrr"
|
|
"github.com/emersion/go-sasl"
|
|
"github.com/emersion/go-smtp"
|
|
)
|
|
|
|
type Backend struct {
|
|
config *Config
|
|
}
|
|
|
|
func (bkd *Backend) sendNotification(recipient ConfigRecipient, email ReceivedEmail) error {
|
|
if recipient.Target == "" {
|
|
slog.Warn("no target provided for recipient", slog.String("recipient", strings.Join(recipient.Addresses, ",")))
|
|
return nil
|
|
}
|
|
|
|
urlParams := url.Values{
|
|
"title": {email.Msg.Header.Get("Subject")},
|
|
}
|
|
|
|
destinationURL := recipient.GetTargetURL()
|
|
destinationURL.RawQuery = urlParams.Encode()
|
|
|
|
body, err := email.Body()
|
|
if err != nil {
|
|
slog.Error("Error getting email body", slog.String("err", err.Error()))
|
|
return fmt.Errorf("failed to get email body: %w", err)
|
|
}
|
|
|
|
if err := shoutrrr.Send(destinationURL.String(), body); err != nil {
|
|
slog.Error("Error sending message", slog.String("err", err.Error()))
|
|
return fmt.Errorf("failed to send notification: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (bkd *Backend) NewSession(c *smtp.Conn) (smtp.Session, error) {
|
|
return &Session{
|
|
forwarderFunc: bkd.forwardEmail,
|
|
config: bkd.config,
|
|
}, nil
|
|
}
|
|
|
|
func (bkd *Backend) forwardEmail(email ReceivedEmail) error {
|
|
slog.Info("forwading message", slog.String("to", strings.Join(email.Recipients, ",")))
|
|
|
|
// Try to match configured recipients first
|
|
matched := false
|
|
for _, r := range bkd.config.Recipients {
|
|
for _, a := range email.Recipients {
|
|
if slices.Contains(r.Addresses, a) {
|
|
if err := bkd.sendNotification(r, email); err != nil {
|
|
return err
|
|
}
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
if matched {
|
|
break
|
|
}
|
|
}
|
|
|
|
// If no recipient matched and catch-all is configured, use it
|
|
if !matched && bkd.config.CatchAll != nil {
|
|
slog.Info("using catch-all recipient for unmatched email")
|
|
if err := bkd.sendNotification(*bkd.config.CatchAll, email); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type Session struct {
|
|
addresses []string
|
|
|
|
config *Config
|
|
|
|
forwarderFunc func(ReceivedEmail) error
|
|
}
|
|
|
|
func (s *Session) AuthMechanisms() []string {
|
|
return []string{sasl.Plain}
|
|
}
|
|
|
|
func (s *Session) Auth(mech string) (sasl.Server, error) {
|
|
return sasl.NewPlainServer(func(identity, username, password string) error {
|
|
if username != s.config.Username && password != s.config.Password {
|
|
return fmt.Errorf("invalid credentials")
|
|
}
|
|
return nil
|
|
}), nil
|
|
}
|
|
|
|
func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
|
|
slog.Debug("Mail from", slog.String("from", from))
|
|
return nil
|
|
}
|
|
|
|
func (s *Session) Rcpt(to string, opts *smtp.RcptOptions) error {
|
|
slog.Debug("Rcpt to", slog.String("to", to))
|
|
s.addresses = append(s.addresses, to)
|
|
return nil
|
|
}
|
|
|
|
func (s *Session) Data(r io.Reader) error {
|
|
msg, err := mail.ReadMessage(r)
|
|
if err != nil {
|
|
slog.Error("Error reading data", slog.String("err", err.Error()))
|
|
return fmt.Errorf("Error reading data: %w", err)
|
|
}
|
|
|
|
slog.Info("Received email", slog.String("destination", strings.Join(s.addresses, ",")))
|
|
|
|
if err := s.forwarderFunc(ReceivedEmail{
|
|
Recipients: s.addresses,
|
|
Msg: msg,
|
|
}); err != nil {
|
|
slog.Error("Error forwarding email", slog.String("err", err.Error()))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Session) Reset() {}
|
|
|
|
func (s *Session) Logout() error {
|
|
return nil
|
|
}
|