From 4f01edb9230f58ff84b0dd892c931ec8ac9aad55 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 13 Sep 2022 12:56:08 +0200 Subject: move src out of srv, clean up default.nix and Makefile --- src/mailinglist/mailer.go | 143 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 src/mailinglist/mailer.go (limited to 'src/mailinglist/mailer.go') diff --git a/src/mailinglist/mailer.go b/src/mailinglist/mailer.go new file mode 100644 index 0000000..07d6c3a --- /dev/null +++ b/src/mailinglist/mailer.go @@ -0,0 +1,143 @@ +package mailinglist + +import ( + "context" + "errors" + "strings" + + "github.com/emersion/go-sasl" + "github.com/emersion/go-smtp" + "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" + "github.com/mediocregopher/mediocre-go-lib/v2/mctx" + "github.com/mediocregopher/mediocre-go-lib/v2/mlog" +) + +// Mailer is used to deliver emails to arbitrary recipients. +type Mailer interface { + Send(to, subject, body string) error +} + +type logMailer struct { + logger *mlog.Logger +} + +// NewLogMailer returns a Mailer instance which will not actually send any +// emails, it will only log to the given Logger when Send is called. +func NewLogMailer(logger *mlog.Logger) Mailer { + return &logMailer{logger: logger} +} + +func (l *logMailer) Send(to, subject, body string) error { + ctx := mctx.Annotate(context.Background(), + "to", to, + "subject", subject, + ) + l.logger.Info(ctx, "would have sent email") + return nil +} + +// NullMailer acts as a Mailer but actually just does nothing. +var NullMailer = nullMailer{} + +type nullMailer struct{} + +func (nullMailer) Send(to, subject, body string) error { + return nil +} + +// MailerParams are used to initialize a new Mailer instance. +type MailerParams struct { + SMTPAddr string + + // Optional, if not given then no auth is attempted. + SMTPAuth sasl.Client + + // The sending email address to use for all emails being sent. + SendAs string +} + +// SetupCfg implement the cfg.Cfger interface. +func (m *MailerParams) SetupCfg(cfg *cfg.Cfg) { + + cfg.StringVar(&m.SMTPAddr, "ml-smtp-addr", "", "Address of SMTP server to use for sending emails for the mailing list") + smtpAuthStr := cfg.String("ml-smtp-auth", "", "user:pass to use when authenticating with the mailing list SMTP server. The given user will also be used as the From address.") + + cfg.OnInit(func(ctx context.Context) error { + if m.SMTPAddr == "" { + return nil + } + + smtpAuthParts := strings.SplitN(*smtpAuthStr, ":", 2) + if len(smtpAuthParts) < 2 { + return errors.New("invalid -ml-smtp-auth") + } + + m.SMTPAuth = sasl.NewPlainClient("", smtpAuthParts[0], smtpAuthParts[1]) + m.SendAs = smtpAuthParts[0] + + return nil + }) +} + +// Annotate implements mctx.Annotator interface. +func (m *MailerParams) Annotate(a mctx.Annotations) { + if m.SMTPAddr == "" { + return + } + + a["smtpAddr"] = m.SMTPAddr + a["smtpSendAs"] = m.SendAs +} + +type mailer struct { + params MailerParams +} + +// NewMailer initializes and returns a Mailer which will use an external SMTP +// server to deliver email. +func NewMailer(params MailerParams) Mailer { + return &mailer{ + params: params, + } +} + +func (m *mailer) Send(to, subject, body string) error { + + msg := []byte("From: " + m.params.SendAs + "\r\n" + + "To: " + to + "\r\n" + + "Subject: " + subject + "\r\n\r\n" + + body + "\r\n") + + c, err := smtp.Dial(m.params.SMTPAddr) + if err != nil { + return err + } + defer c.Close() + + if err = c.Auth(m.params.SMTPAuth); err != nil { + return err + } + + if err = c.Mail(m.params.SendAs, nil); err != nil { + return err + } + + if err = c.Rcpt(to); err != nil { + return err + } + + w, err := c.Data() + if err != nil { + return err + } + + if _, err = w.Write(msg); err != nil { + return err + } + + if err = w.Close(); err != nil { + return err + } + + return c.Quit() +} -- cgit v1.2.3