Integrate new SMTP pool lib

This commit is contained in:
Kailash Nadh 2020-05-16 22:41:30 +05:30
parent e58b2fa669
commit 9d3ca357f6
7 changed files with 68 additions and 56 deletions

View File

@ -91,8 +91,7 @@ max_idle = 10
username = "xxxxx" username = "xxxxx"
password = "" password = ""
# Optional. Inform listmonk on which email format to use # Format to send e-mails in: html|plain|both.
# {html|plain|both} default: both
email_format = "both" email_format = "both"
# Optional. Some SMTP servers require a FQDN in the hostname. # Optional. Some SMTP servers require a FQDN in the hostname.
@ -100,12 +99,19 @@ max_idle = 10
# hostname should be used. # hostname should be used.
hello_hostname = "" hello_hostname = ""
# Maximum time (milliseconds) to wait per e-mail push.
send_timeout = 5000
# Maximum concurrent connections to the SMTP server. # Maximum concurrent connections to the SMTP server.
max_conns = 10 max_conns = 10
# Time to wait for new activity on a connection before closing
# it and removing it from the pool.
idle_timeout = "15s"
# Message send / wait timeout.
wait_timeout = "5s"
# The number of times a message should be retried if sending fails.
max_msg_retries = 2
[smtp.postal] [smtp.postal]
enabled = false enabled = false
host = "my.smtp.server2" host = "my.smtp.server2"
@ -116,16 +122,27 @@ max_idle = 10
username = "xxxxx" username = "xxxxx"
password = "" password = ""
# Optional. Inform listmonk on which email format to use # Format to send e-mails in: html|plain|both.
# {html|plain|both} default: both
email_format = "both" email_format = "both"
# Maximum time (milliseconds) to wait per e-mail push. # Optional. Some SMTP servers require a FQDN in the hostname.
send_timeout = 5000 # By default, HELLOs go with "localhost". Set this if a custom
# hostname should be used.
hello_hostname = ""
# Maximum concurrent connections to the SMTP server. # Maximum concurrent connections to the SMTP server.
max_conns = 10 max_conns = 10
# Time to wait for new activity on a connection before closing
# it and removing it from the pool.
idle_timeout = "15s"
# Message send / wait timeout.
wait_timeout = "5s"
# The number of times a message should be retried if sending fails.
max_msg_retries = 2
[upload] [upload]
# File storage backend. "filesystem" or "s3". # File storage backend. "filesystem" or "s3".

6
go.mod
View File

@ -1,13 +1,14 @@
module github.com/knadh/listmonk module github.com/knadh/listmonk
go 1.13
require ( require (
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/gofrs/uuid v3.2.0+incompatible github.com/gofrs/uuid v3.2.0+incompatible
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195 github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195
github.com/jmoiron/sqlx v1.2.0 github.com/jmoiron/sqlx v1.2.0
github.com/jordan-wright/email v0.0.0-20200307200233-de844847de93
github.com/knadh/goyesql/v2 v2.1.1 github.com/knadh/goyesql/v2 v2.1.1
github.com/knadh/koanf v0.8.1 github.com/knadh/koanf v0.8.1
github.com/knadh/smtppool v0.2.0
github.com/knadh/stuffbin v1.1.0 github.com/knadh/stuffbin v1.1.0
github.com/labstack/echo v3.3.10+incompatible github.com/labstack/echo v3.3.10+incompatible
github.com/labstack/gommon v0.3.0 // indirect github.com/labstack/gommon v0.3.0 // indirect
@ -22,6 +23,3 @@ require (
jaytaylor.com/html2text v0.0.0-20200220170450-61d9dc4d7195 jaytaylor.com/html2text v0.0.0-20200220170450-61d9dc4d7195
) )
replace github.com/jordan-wright/email => github.com/knadh/email v0.0.0-20200206100304-6d2c7064c2e8
go 1.13

4
go.sum
View File

@ -29,6 +29,10 @@ github.com/knadh/goyesql/v2 v2.1.1 h1:Orp5ldaxPM4ozKHfu1m7p6iolJFXDGOpF3/jyOgO6l
github.com/knadh/goyesql/v2 v2.1.1/go.mod h1:pMzCA130/ZhEIoMmSmbEFXor3A2dxl5L+JllAc/l64s= github.com/knadh/goyesql/v2 v2.1.1/go.mod h1:pMzCA130/ZhEIoMmSmbEFXor3A2dxl5L+JllAc/l64s=
github.com/knadh/koanf v0.8.1 h1:4VLACWqrkWRQIup3ooq6lOnaSbOJSNO+YVXnJn/NPZ8= github.com/knadh/koanf v0.8.1 h1:4VLACWqrkWRQIup3ooq6lOnaSbOJSNO+YVXnJn/NPZ8=
github.com/knadh/koanf v0.8.1/go.mod h1:kVvmDbXnBtW49Czi4c1M+nnOWF0YSNZ8BaKvE/bCO1w= github.com/knadh/koanf v0.8.1/go.mod h1:kVvmDbXnBtW49Czi4c1M+nnOWF0YSNZ8BaKvE/bCO1w=
github.com/knadh/smtppool v0.1.1 h1:pSi1Gc5TXOaN/Z/YiqfZbk/vd9dqzXzAfQiss0QSGQU=
github.com/knadh/smtppool v0.1.1/go.mod h1:3DJHouXAgPDBz0kC50HukOsdapYSwIEfJGwuip46oCA=
github.com/knadh/smtppool v0.2.0 h1:+llTWRljNIVg05MMu9TiefELTNwblexjsd1ALAPXZUs=
github.com/knadh/smtppool v0.2.0/go.mod h1:3DJHouXAgPDBz0kC50HukOsdapYSwIEfJGwuip46oCA=
github.com/knadh/stuffbin v1.0.0 h1:NQon6PTpLXies4bRFhS3VpLCf6y+jn6YVXU3i2wPQ+M= github.com/knadh/stuffbin v1.0.0 h1:NQon6PTpLXies4bRFhS3VpLCf6y+jn6YVXU3i2wPQ+M=
github.com/knadh/stuffbin v1.0.0/go.mod h1:yVCFaWaKPubSNibBsTAJ939q2ABHudJQxRWZWV5yh+4= github.com/knadh/stuffbin v1.0.0/go.mod h1:yVCFaWaKPubSNibBsTAJ939q2ABHudJQxRWZWV5yh+4=
github.com/knadh/stuffbin v1.1.0 h1:f5S5BHzZALjuJEgTIOMC9NidEnBJM7Ze6Lu1GHR/lwU= github.com/knadh/stuffbin v1.1.0 h1:f5S5BHzZALjuJEgTIOMC9NidEnBJM7Ze6Lu1GHR/lwU=

15
init.go
View File

@ -7,11 +7,11 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github.com/knadh/goyesql/v2" "github.com/knadh/goyesql/v2"
goyesqlx "github.com/knadh/goyesql/v2/sqlx" goyesqlx "github.com/knadh/goyesql/v2/sqlx"
"github.com/knadh/koanf"
"github.com/knadh/koanf/maps" "github.com/knadh/koanf/maps"
"github.com/knadh/listmonk/internal/manager" "github.com/knadh/listmonk/internal/manager"
"github.com/knadh/listmonk/internal/media" "github.com/knadh/listmonk/internal/media"
@ -214,28 +214,27 @@ func initImporter(q *Queries, db *sqlx.DB, app *App) *subimporter.Importer {
}) })
} }
// initMessengers initializes various messaging backends. // initMessengers initializes various messenger backends.
func initMessengers(m *manager.Manager) messenger.Messenger { func initMessengers(m *manager.Manager) messenger.Messenger {
// Load SMTP configurations for the default e-mail Messenger.
var ( var (
mapKeys = ko.MapKeys("smtp") mapKeys = ko.MapKeys("smtp")
srv = make([]messenger.Server, 0, len(mapKeys)) srv = make([]messenger.Server, 0, len(mapKeys))
) )
// Load the default SMTP messengers.
for _, name := range mapKeys { for _, name := range mapKeys {
if !ko.Bool(fmt.Sprintf("smtp.%s.enabled", name)) { if !ko.Bool(fmt.Sprintf("smtp.%s.enabled", name)) {
lo.Printf("skipped SMTP: %s", name) lo.Printf("skipped SMTP: %s", name)
continue continue
} }
var s messenger.Server // Read the SMTP config.
if err := ko.Unmarshal("smtp."+name, &s); err != nil { s := messenger.Server{Name: name}
if err := ko.UnmarshalWithConf("smtp."+name, &s, koanf.UnmarshalConf{Tag: "json"}); err != nil {
lo.Fatalf("error loading SMTP: %v", err) lo.Fatalf("error loading SMTP: %v", err)
} }
s.Name = name
s.SendTimeout *= time.Millisecond
srv = append(srv, s)
srv = append(srv, s)
lo.Printf("loaded SMTP: %s (%s@%s)", s.Name, s.Username, s.Host) lo.Printf("loaded SMTP: %s (%s@%s)", s.Name, s.Username, s.Host)
} }

View File

@ -5,10 +5,9 @@ import (
"fmt" "fmt"
"math/rand" "math/rand"
"net/smtp" "net/smtp"
"time"
"github.com/jaytaylor/html2text" "github.com/jaytaylor/html2text"
"github.com/jordan-wright/email" "github.com/knadh/smtppool"
) )
const emName = "email" const emName = "email"
@ -21,21 +20,21 @@ type loginAuth struct {
// Server represents an SMTP server's credentials. // Server represents an SMTP server's credentials.
type Server struct { type Server struct {
Name string Name string
Host string `koanf:"host"` Username string `json:"username"`
Port int `koanf:"port"` Password string `json:"password"`
AuthProtocol string `koanf:"auth_protocol"` AuthProtocol string `json:"auth_protocol"`
Username string `koanf:"username"` EmailFormat string `json:"email_format"`
Password string `koanf:"password"`
EmailFormat string `koanf:"email_format"`
HelloHostname string `koanf:"hello_hostname"`
SendTimeout time.Duration `koanf:"send_timeout"`
MaxConns int `koanf:"max_conns"`
mailer *email.Pool // 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
} }
type emailer struct { // Emailer is the SMTP e-mail messenger.
type Emailer struct {
servers map[string]*Server servers map[string]*Server
serverNames []string serverNames []string
numServers int numServers int
@ -43,8 +42,8 @@ type emailer struct {
// NewEmailer creates and returns an e-mail Messenger backend. // NewEmailer creates and returns an e-mail Messenger backend.
// It takes multiple SMTP configurations. // It takes multiple SMTP configurations.
func NewEmailer(srv ...Server) (Messenger, error) { func NewEmailer(srv ...Server) (*Emailer, error) {
e := &emailer{ e := &Emailer{
servers: make(map[string]*Server), servers: make(map[string]*Server),
} }
@ -62,18 +61,14 @@ func NewEmailer(srv ...Server) (Messenger, error) {
default: default:
return nil, fmt.Errorf("unknown SMTP auth type '%s'", s.AuthProtocol) return nil, fmt.Errorf("unknown SMTP auth type '%s'", s.AuthProtocol)
} }
s.Opt.Auth = auth
pool, err := email.NewPool(fmt.Sprintf("%s:%d", s.Host, s.Port), s.MaxConns, auth) pool, err := smtppool.New(s.Opt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Optional SMTP HELLO hostname. s.pool = pool
if server.HelloHostname != "" {
pool.SetHelloHostname(server.HelloHostname)
}
s.mailer = pool
e.servers[s.Name] = &s e.servers[s.Name] = &s
e.serverNames = append(e.serverNames, s.Name) e.serverNames = append(e.serverNames, s.Name)
} }
@ -83,12 +78,12 @@ func NewEmailer(srv ...Server) (Messenger, error) {
} }
// Name returns the Server's name. // Name returns the Server's name.
func (e *emailer) Name() string { func (e *Emailer) Name() string {
return emName return emName
} }
// Push pushes a message to the server. // Push pushes a message to the server.
func (e *emailer) Push(fromAddr string, toAddr []string, subject string, m []byte, atts []*Attachment) error { func (e *Emailer) Push(fromAddr string, toAddr []string, subject string, m []byte, atts []Attachment) error {
var key string var key string
// If there are more than one SMTP servers, send to a random // If there are more than one SMTP servers, send to a random
@ -100,11 +95,11 @@ func (e *emailer) Push(fromAddr string, toAddr []string, subject string, m []byt
} }
// Are there attachments? // Are there attachments?
var files []*email.Attachment var files []smtppool.Attachment
if atts != nil { if atts != nil {
files = make([]*email.Attachment, 0, len(atts)) files = make([]smtppool.Attachment, 0, len(atts))
for _, f := range atts { for _, f := range atts {
a := &email.Attachment{ a := smtppool.Attachment{
Filename: f.Name, Filename: f.Name,
Header: f.Header, Header: f.Header,
Content: make([]byte, len(f.Content)), Content: make([]byte, len(f.Content)),
@ -120,7 +115,7 @@ func (e *emailer) Push(fromAddr string, toAddr []string, subject string, m []byt
} }
srv := e.servers[key] srv := e.servers[key]
em := &email.Email{ em := smtppool.Email{
From: fromAddr, From: fromAddr,
To: toAddr, To: toAddr,
Subject: subject, Subject: subject,
@ -137,11 +132,11 @@ func (e *emailer) Push(fromAddr string, toAddr []string, subject string, m []byt
em.Text = []byte(mtext) em.Text = []byte(mtext)
} }
return srv.mailer.Send(em, srv.SendTimeout) return srv.pool.Send(em)
} }
// Flush flushes the message queue to the server. // Flush flushes the message queue to the server.
func (e *emailer) Flush() error { func (e *Emailer) Flush() error {
return nil return nil
} }

View File

@ -6,8 +6,7 @@ import "net/textproto"
// for instance, e-mail, SMS etc. // for instance, e-mail, SMS etc.
type Messenger interface { type Messenger interface {
Name() string Name() string
Push(fromAddr string, toAddr []string, subject string, message []byte, atts []Attachment) error
Push(fromAddr string, toAddr []string, subject string, message []byte, atts []*Attachment) error
Flush() error Flush() error
} }

View File

@ -360,8 +360,8 @@ func handleSelfExportSubscriberData(c echo.Context) error {
[]string{data.Email}, []string{data.Email},
"Your profile data", "Your profile data",
msg.Bytes(), msg.Bytes(),
[]*messenger.Attachment{ []messenger.Attachment{
&messenger.Attachment{ {
Name: fname, Name: fname,
Content: b, Content: b,
Header: messenger.MakeAttachmentHeader(fname, "base64"), Header: messenger.MakeAttachmentHeader(fname, "base64"),