Jelajahi Sumber

Integrate new SMTP pool lib

Kailash Nadh 5 tahun lalu
induk
melakukan
9d3ca357f6
7 mengubah file dengan 69 tambahan dan 57 penghapusan
  1. 26 9
      config.toml.sample
  2. 2 4
      go.mod
  3. 4 0
      go.sum
  4. 7 8
      init.go
  5. 27 32
      internal/messenger/emailer.go
  6. 1 2
      internal/messenger/messenger.go
  7. 2 2
      public.go

+ 26 - 9
config.toml.sample

@@ -91,8 +91,7 @@ max_idle = 10
         username = "xxxxx"
         username = "xxxxx"
         password = ""
         password = ""
 
 
-        # Optional. Inform listmonk on which email format to use
-        # {html|plain|both} default: both
+        # Format to send e-mails in: html|plain|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
-        # {html|plain|both} default: both
+        # Format to send e-mails in: html|plain|both.
         email_format = "both"
         email_format = "both"
 
 
-        # Maximum time (milliseconds) to wait per e-mail push.
-        send_timeout = 5000
+        # Optional. Some SMTP servers require a FQDN in the hostname.
+        # 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".

+ 2 - 4
go.mod

@@ -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 - 0
go.sum

@@ -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=

+ 7 - 8
init.go

@@ -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
-		if err := ko.Unmarshal("smtp."+name, &s); err != nil {
+		// Read the SMTP config.
+		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)
 	}
 	}
 
 

+ 27 - 32
internal/messenger/emailer.go

@@ -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
-	Host          string        `koanf:"host"`
-	Port          int           `koanf:"port"`
-	AuthProtocol  string        `koanf:"auth_protocol"`
-	Username      string        `koanf:"username"`
-	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
+	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
 }
 }
 
 
-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) {
-	e := &emailer{
+func NewEmailer(srv ...Server) (*Emailer, error) {
+	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.
-		if server.HelloHostname != "" {
-			pool.SetHelloHostname(server.HelloHostname)
-		}
-
-		s.mailer = pool
+		s.pool = 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
 }
 }
 
 

+ 1 - 2
internal/messenger/messenger.go

@@ -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
 }
 }
 
 

+ 2 - 2
public.go

@@ -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"),