From 78cfc5d87a7dcb762467aaa82fcc80cb4e24974b Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Sat, 14 Dec 2024 23:53:55 +0100 Subject: [PATCH] email: allow sending raw emails The current email.Enqueue() function only allows sending messages with the server's default settings. In order to support sending any arbitrary message, we need to expose a lower level API taking a raw message.Entity pointer. This new API will be reused in a future reimplementation of the lists.sr.ht ingress agent to forward messages to subscribers. Add email.EnqueueRaw() that calls email.SendRaw(). Retry sending 10 times in the case of failure. Signed-off-by: Robin Jarry --- email/send.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ email/worker.go | 21 +++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/email/send.go b/email/send.go index 27fe52ca2824c7c916712c9fb6686b711ec739eb..48b021a91fafe99c37de3fe4ba16e82b44a72b1d 100644 --- a/email/send.go +++ b/email/send.go @@ -6,6 +6,7 @@ import ( "io" "strconv" + "github.com/emersion/go-message" _ "github.com/emersion/go-message/charset" "github.com/emersion/go-message/mail" "github.com/emersion/go-sasl" @@ -131,3 +132,48 @@ func Send(ctx context.Context, msg io.Reader, rcpts []string) error { } return err } + +// Sends a raw email. Blocks until it's sent or an error occurs. +func SendRaw(ctx context.Context, email *message.Entity, to []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 + } + + var writer io.WriteCloser + var err error + + err = worker.client.Mail(worker.sender.Address, nil) + if err != nil { + goto end + } + for _, rcpt := range to { + if err := worker.client.Rcpt(rcpt, nil); err != nil { + return err + } + } + writer, err = worker.client.Data() + if err != nil { + goto end + } + err = email.WriteTo(writer) + if err != nil { + writer.Close() + goto end + } + err = writer.Close() +end: + if err != nil { + worker.client.Close() + worker.client = nil + } + return err +} diff --git a/email/worker.go b/email/worker.go index 1c6cda2445c0a846ba1c802224f25b1c473f3af2..3d918721549512592c37f14c68e5a476cccfe0d2 100644 --- a/email/worker.go +++ b/email/worker.go @@ -15,6 +15,7 @@ import ( work "git.sr.ht/~sircmpwn/dowork" "github.com/ProtonMail/go-crypto/openpgp" + "github.com/emersion/go-message" _ "github.com/emersion/go-message/charset" "github.com/emersion/go-message/mail" "github.com/emersion/go-pgpmail" @@ -172,6 +173,26 @@ func EnqueueStd(ctx context.Context, header mail.Header, return queue.Enqueue(NewTask(&buf, rcpts)) } +func EnqueueRaw(ctx context.Context, email *message.Entity, to []string) error { + queue := ForContext(ctx).queue + return queue.Enqueue(work.NewTask(func(ctx context.Context) error { + err := SendRaw(ctx, email, to) + if err != nil { + log.Printf("Error sending mail: %v", err) + } + return err + }).Retries(10).After(func(ctx context.Context, task *work.Task) { + msgID := email.Header.Get("Message-ID") + if task.Result() == nil { + log.Printf("Mail %s sent to %d recipients after %d attempts", + msgID, len(to), task.Attempts()) + } else { + log.Printf("Mail %s failed after %d attempts: %v", + msgID, task.Attempts(), task.Result()) + } + })) +} + type nopWriteCloser struct { io.Writer }