mirror of
https://github.com/mjl-/mox.git
synced 2025-07-14 18:54:37 +03:00
queue: deliver to multiple recipients in a single smtp transaction
transferring the data only once. we only do this when the recipient domains are the same. when queuing, we now take care to set the same NextAttempt timestamp, so queued messages are actually eligable for combined delivery. this adds a DeliverMultiple to the smtp client. for pipelined requests, it will send all RCPT TO (and MAIL and DATA) in one go, and handles the various responses and error conditions, returning either an overal error, or per recipient smtp responses. the results of the smtp LIMITS extension are also available in the smtp client now. this also takes the "LIMITS RCPTMAX" smtp extension into account: if the server only accepts a single recipient, we won't send multiple. if a server doesn't announce a RCPTMAX limit, but still has one (like mox does for non-spf-verified transactions), we'll recognize code 452 and 552 (for historic reasons) as temporary error, and try again in a separate transaction immediately after. we don't yet implement "LIMITS MAILMAX", doesn't seem likely in practice.
This commit is contained in:
@ -3,6 +3,7 @@ package smtpserver
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mjl-/mox/dsn"
|
||||
"github.com/mjl-/mox/mlog"
|
||||
@ -53,7 +54,7 @@ func queueDSN(ctx context.Context, log mlog.Log, c *conn, rcptTo smtp.Path, m ds
|
||||
if requireTLS {
|
||||
reqTLS = &requireTLS
|
||||
}
|
||||
qm := queue.MakeMsg(smtp.Path{}, rcptTo, has8bit, smtputf8, int64(len(buf)), m.MessageID, nil, reqTLS)
|
||||
qm := queue.MakeMsg(smtp.Path{}, rcptTo, has8bit, smtputf8, int64(len(buf)), m.MessageID, nil, reqTLS, time.Now())
|
||||
qm.DSNUTF8 = bufUTF8
|
||||
if err := queue.Add(ctx, c.log, "", f, qm); err != nil {
|
||||
return err
|
||||
|
@ -1834,7 +1834,13 @@ func (c *conn) cmdData(p *parser) {
|
||||
tlsComment := mox.TLSReceivedComment(c.log, tlsConn.ConnectionState())
|
||||
recvHdr.Add(" ", tlsComment...)
|
||||
}
|
||||
recvHdr.Add(" ", "for", "<"+rcptTo+">;", time.Now().Format(message.RFC5322Z))
|
||||
// We leave out an empty "for" clause. This is empty for messages submitted to
|
||||
// multiple recipients, so the message stays identical and a single smtp
|
||||
// transaction can deliver, only transferring the data once.
|
||||
if rcptTo != "" {
|
||||
recvHdr.Add(" ", "for", "<"+rcptTo+">;")
|
||||
}
|
||||
recvHdr.Add(" ", time.Now().Format(message.RFC5322Z))
|
||||
return recvHdr.String()
|
||||
}
|
||||
|
||||
@ -1979,6 +1985,7 @@ func (c *conn) submit(ctx context.Context, recvHdrFor func(string) string, msgWr
|
||||
// We always deliver through the queue. It would be more efficient to deliver
|
||||
// directly, but we don't want to circumvent all the anti-spam measures. Accounts
|
||||
// on a single mox instance should be allowed to block each other.
|
||||
now := time.Now()
|
||||
qml := make([]queue.Msg, len(c.recipients))
|
||||
for i, rcptAcc := range c.recipients {
|
||||
if Localserve {
|
||||
@ -1993,9 +2000,16 @@ func (c *conn) submit(ctx context.Context, recvHdrFor func(string) string, msgWr
|
||||
}
|
||||
}
|
||||
|
||||
xmsgPrefix := append([]byte(recvHdrFor(rcptAcc.rcptTo.String())), msgPrefix...)
|
||||
// For multiple recipients, we don't make each message prefix unique, leaving out
|
||||
// the "for" clause in the Received header. This allows the queue to deliver the
|
||||
// messages in a single smtp transaction.
|
||||
var rcptTo string
|
||||
if len(c.recipients) == 1 {
|
||||
rcptTo = rcptAcc.rcptTo.String()
|
||||
}
|
||||
xmsgPrefix := append([]byte(recvHdrFor(rcptTo)), msgPrefix...)
|
||||
msgSize := int64(len(xmsgPrefix)) + msgWriter.Size
|
||||
qm := queue.MakeMsg(*c.mailFrom, rcptAcc.rcptTo, msgWriter.Has8bit, c.smtputf8, msgSize, messageID, xmsgPrefix, c.requireTLS)
|
||||
qm := queue.MakeMsg(*c.mailFrom, rcptAcc.rcptTo, msgWriter.Has8bit, c.smtputf8, msgSize, messageID, xmsgPrefix, c.requireTLS, now)
|
||||
if !c.futureRelease.IsZero() {
|
||||
qm.NextAttempt = c.futureRelease
|
||||
qm.FutureReleaseRequest = c.futureReleaseRequest
|
||||
|
Reference in New Issue
Block a user