2021-07-31 22:45:49 +00:00
package mailinglist
import (
2021-08-08 02:38:37 +00:00
"context"
"errors"
"strings"
2021-07-31 22:45:49 +00:00
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
2021-08-08 02:38:37 +00:00
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
2021-08-30 03:34:02 +00:00
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
2021-07-31 22:45:49 +00:00
)
// Mailer is used to deliver emails to arbitrary recipients.
type Mailer interface {
Send ( to , subject , body string ) error
}
2021-08-30 03:34:02 +00:00
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
}
2021-08-07 02:34:18 +00:00
// 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
}
2022-05-06 02:17:59 +00:00
// MailerParams are used to initialize a new Mailer instance.
2021-07-31 22:45:49 +00:00
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
}
2021-08-08 02:38:37 +00:00
// 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
}
2021-07-31 22:45:49 +00:00
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 ( )
}