From bbcb2a1096d86b989e5d82dc45289daec38ba4a6 Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Sat, 14 Dec 2024 23:53:54 +0100 Subject: [PATCH] email: keep smtp client connection alive Instead of connecting and disconnecting each and every time we need to send an email, keep the SMTP connection alive attached to a context variable. Before sending an email, check if the connection is still valid and reconnect if it is not. Signed-off-by: Robin Jarry --- email/send.go | 23 ++++++++++++++++++----- email/worker.go | 15 +++++++++++---- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/email/send.go b/email/send.go index 2c75dac82f2a0ca032ec9145248d7fed6e152eb3..27fe52ca2824c7c916712c9fb6686b711ec739eb 100644 --- a/email/send.go +++ b/email/send.go @@ -4,10 +4,10 @@ import ( "context" "fmt" "io" - "net/mail" "strconv" _ "github.com/emersion/go-message/charset" + "github.com/emersion/go-message/mail" "github.com/emersion/go-sasl" "github.com/emersion/go-smtp" @@ -111,10 +111,23 @@ func mailSetup(ctx context.Context) (*smtp.Client, *mail.Address, error) { // Sends an email. Blocks until it's sent or an error occurs. func Send(ctx context.Context, msg io.Reader, rcpts []string) error { - c, sender, err := mailSetup(ctx) + 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 { - return err + worker.client.Close() + worker.client = nil } - defer c.Close() - return c.SendMail(sender.Address, rcpts, msg) + return err } diff --git a/email/worker.go b/email/worker.go index 7c75478bc4cc7e747ae7b8467a00746e2081c954..1c6cda2445c0a846ba1c802224f25b1c473f3af2 100644 --- a/email/worker.go +++ b/email/worker.go @@ -18,6 +18,7 @@ import ( _ "github.com/emersion/go-message/charset" "github.com/emersion/go-message/mail" "github.com/emersion/go-pgpmail" + "github.com/emersion/go-smtp" "github.com/vaughan0/go-ini" ) @@ -27,6 +28,12 @@ type contextKey struct { name string } +type workerContext struct { + client *smtp.Client + sender *mail.Address + queue *Queue +} + // Returns a task which will send this email for the work queue. If the caller // does not need to customize the task parameters, the Enqueue function may be // more desirable. @@ -81,7 +88,7 @@ func prepareSigned(header mail.Header, buf *bytes.Buffer, func EnqueueStd(ctx context.Context, header mail.Header, bodyReader io.Reader, rcptKey *string) error { - queue := ForContext(ctx) + queue := ForContext(ctx).queue to, err := header.AddressList("To") if err != nil { @@ -246,8 +253,8 @@ func NewQueue(conf ini.File) *Queue { } // Returns the email worker for this context. -func ForContext(ctx context.Context) *Queue { - q, ok := ctx.Value(emailCtxKey).(*Queue) +func ForContext(ctx context.Context) *workerContext { + q, ok := ctx.Value(emailCtxKey).(*workerContext) if !ok { panic(errors.New("No email worker for this context")) } @@ -256,7 +263,7 @@ func ForContext(ctx context.Context) *Queue { // Returns a context which includes the given mail worker. func Context(ctx context.Context, queue *Queue) context.Context { - return context.WithValue(ctx, emailCtxKey, queue) + return context.WithValue(ctx, emailCtxKey, &workerContext{queue: queue}) } // Adds HTTP middleware to provide an email work queue to this context.