~bigbes/core-go

78cfc5d87a7dcb762467aaa82fcc80cb4e24974b — Robin Jarry 1 year, 8 months ago bbcb2a1
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 <robin@jarry.cc>
2 files changed, 67 insertions(+), 0 deletions(-)

M email/send.go
M email/worker.go
M email/send.go => email/send.go +46 -0
@@ 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
}

M email/worker.go => email/worker.go +21 -0
@@ 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
}