package email
import (
"context"
"fmt"
"io"
"strconv"
_ "github.com/emersion/go-message/charset"
"github.com/emersion/go-message/mail"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
"git.sr.ht/~sircmpwn/core-go/config"
)
type mailConfig struct {
host string
port int
from string
enctype string
authtype string
user string
pass string
}
func mailSetup(ctx context.Context) (*smtp.Client, *mail.Address, error) {
conf := config.ForContext(ctx)
mailconf := &mailConfig{enctype: "starttls", authtype: "plain"}
var err error
portStr, ok := conf.Get("mail", "smtp-port")
if !ok {
panic(fmt.Errorf("[mail]smtp-port unset"))
}
mailconf.port, err = strconv.Atoi(portStr)
if err != nil {
panic(fmt.Errorf("unable to parse [mail]smtp-port (must be integer)"))
}
if mailconf.host, ok = conf.Get("mail", "smtp-host"); !ok {
panic(fmt.Errorf("missing SMTP configuration options [smtp-host]"))
}
if mailconf.from, ok = conf.Get("mail", "smtp-from"); !ok {
panic(fmt.Errorf("missing SMTP configuration options [smtp-from]"))
}
sender, err := mail.ParseAddress(mailconf.from)
if err != nil {
panic(err)
}
if enctype, ok := conf.Get("mail", "smtp-encryption"); ok {
switch enctype {
case "starttls", "tls", "insecure":
mailconf.enctype = enctype
default:
panic(fmt.Errorf("invalid SMTP configuration value for [smtp-encryption]"))
}
}
if authtype, ok := conf.Get("mail", "smtp-auth"); ok {
switch authtype {
case "none", "plain":
mailconf.authtype = authtype
default:
panic(fmt.Errorf("invalid SMTP configuration value for [smtp-auth]"))
}
}
if mailconf.authtype == "plain" {
if mailconf.user, ok = conf.Get("mail", "smtp-user"); !ok {
panic(fmt.Errorf("missing SMTP configuration options [smtp-user]"))
}
if mailconf.pass, ok = conf.Get("mail", "smtp-password"); !ok {
panic(fmt.Errorf("missing SMTP configuration options [smtp-password]"))
}
}
var c *smtp.Client
addr := fmt.Sprintf("%s:%d", mailconf.host, mailconf.port)
switch mailconf.enctype {
case "tls":
c, err = smtp.DialTLS(addr, nil)
case "starttls":
c, err = smtp.DialStartTLS(addr, nil)
default:
c, err = smtp.Dial(addr)
}
if err != nil {
return nil, nil, err
}
if err = c.Hello("localhost"); err != nil {
return nil, nil, err
}
if mailconf.authtype == "plain" {
auth := sasl.NewPlainClient("", mailconf.user, mailconf.pass)
if ok, _ := c.Extension("AUTH"); !ok {
panic(fmt.Errorf("smtp: server doesn't support AUTH"))
}
if err = c.Auth(auth); err != nil {
return nil, nil, err
}
}
return c, sender, nil
}
// Sends an email. Blocks until it's sent or an error occurs.
func Send(ctx context.Context, msg io.Reader, rcpts []string) error {
worker := ForContext(ctx)
if worker.client == nil || worker.client.Noop() != nil {
worker.client = nil
c, sender, err := mailSetup(ctx)
if err != nil {
return err
}
worker.client = c
worker.sender = sender
}
err := worker.client.SendMail(worker.sender.Address, rcpts, msg)
if err != nil {
worker.client.Close()
worker.client = nil
}
return err
}