listmonk/internal/messenger/emailer.go

162 lines
3.5 KiB
Go
Raw Normal View History

2018-10-25 15:51:47 +02:00
package messenger
import (
2020-04-01 16:26:40 +02:00
"errors"
2018-10-25 15:51:47 +02:00
"fmt"
"math/rand"
"net/smtp"
"github.com/jaytaylor/html2text"
2020-05-16 19:11:30 +02:00
"github.com/knadh/smtppool"
2018-10-25 15:51:47 +02:00
)
const emName = "email"
2020-04-01 16:26:40 +02:00
// loginAuth is used for enabling SMTP "LOGIN" auth.
type loginAuth struct {
username string
password string
}
2018-10-25 15:51:47 +02:00
// Server represents an SMTP server's credentials.
type Server struct {
2020-05-16 19:11:30 +02:00
Name string
Username string `json:"username"`
Password string `json:"password"`
AuthProtocol string `json:"auth_protocol"`
EmailFormat string `json:"email_format"`
// Rest of the options are embedded directly from the smtppool lib.
// The JSON tag is for config unmarshal to work.
smtppool.Opt `json:",squash"`
pool *smtppool.Pool
2018-10-25 15:51:47 +02:00
}
2020-05-16 19:11:30 +02:00
// Emailer is the SMTP e-mail messenger.
type Emailer struct {
2018-10-25 15:51:47 +02:00
servers map[string]*Server
serverNames []string
numServers int
}
// NewEmailer creates and returns an e-mail Messenger backend.
// It takes multiple SMTP configurations.
2020-05-16 19:11:30 +02:00
func NewEmailer(srv ...Server) (*Emailer, error) {
e := &Emailer{
2018-10-25 15:51:47 +02:00
servers: make(map[string]*Server),
}
for _, server := range srv {
s := server
2018-10-25 15:51:47 +02:00
var auth smtp.Auth
2020-04-01 16:26:40 +02:00
switch s.AuthProtocol {
case "cram":
2018-10-25 15:51:47 +02:00
auth = smtp.CRAMMD5Auth(s.Username, s.Password)
2020-04-01 16:26:40 +02:00
case "plain":
2018-10-25 15:51:47 +02:00
auth = smtp.PlainAuth("", s.Username, s.Password, s.Host)
2020-04-01 16:26:40 +02:00
case "login":
auth = &loginAuth{username: s.Username, password: s.Password}
case "":
default:
2020-05-11 17:30:06 +02:00
return nil, fmt.Errorf("unknown SMTP auth type '%s'", s.AuthProtocol)
2018-10-25 15:51:47 +02:00
}
2020-05-16 19:11:30 +02:00
s.Opt.Auth = auth
2018-10-25 15:51:47 +02:00
2020-05-16 19:11:30 +02:00
pool, err := smtppool.New(s.Opt)
2018-10-25 15:51:47 +02:00
if err != nil {
return nil, err
}
2020-05-16 19:11:30 +02:00
s.pool = pool
2018-10-25 15:51:47 +02:00
e.servers[s.Name] = &s
e.serverNames = append(e.serverNames, s.Name)
}
e.numServers = len(e.serverNames)
return e, nil
}
// Name returns the Server's name.
2020-05-16 19:11:30 +02:00
func (e *Emailer) Name() string {
2018-10-25 15:51:47 +02:00
return emName
}
// Push pushes a message to the server.
2020-05-16 19:11:30 +02:00
func (e *Emailer) Push(fromAddr string, toAddr []string, subject string, m []byte, atts []Attachment) error {
2018-10-25 15:51:47 +02:00
var key string
// If there are more than one SMTP servers, send to a random
// one from the list.
if e.numServers > 1 {
key = e.serverNames[rand.Intn(e.numServers)]
} else {
key = e.serverNames[0]
}
// Are there attachments?
2020-05-16 19:11:30 +02:00
var files []smtppool.Attachment
if atts != nil {
2020-05-16 19:11:30 +02:00
files = make([]smtppool.Attachment, 0, len(atts))
for _, f := range atts {
2020-05-16 19:11:30 +02:00
a := smtppool.Attachment{
Filename: f.Name,
Header: f.Header,
Content: make([]byte, len(f.Content)),
}
copy(a.Content, f.Content)
files = append(files, a)
}
}
mtext, err := html2text.FromString(string(m), html2text.Options{PrettyTables: true})
if err != nil {
return err
}
2018-10-25 15:51:47 +02:00
srv := e.servers[key]
2020-05-16 19:11:30 +02:00
em := smtppool.Email{
From: fromAddr,
To: toAddr,
Subject: subject,
Attachments: files,
}
switch srv.EmailFormat {
case "html":
em.HTML = m
case "plain":
em.Text = []byte(mtext)
default:
em.HTML = m
em.Text = []byte(mtext)
}
2018-10-25 15:51:47 +02:00
2020-05-16 19:11:30 +02:00
return srv.pool.Send(em)
2018-10-25 15:51:47 +02:00
}
// Flush flushes the message queue to the server.
2020-05-16 19:11:30 +02:00
func (e *Emailer) Flush() error {
2018-10-25 15:51:47 +02:00
return nil
}
2020-04-01 16:26:40 +02:00
// https://gist.github.com/andelf/5118732
// Adds support for SMTP LOGIN auth.
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("unkown SMTP fromServer")
}
}
return nil, nil
}