Improved performance by manually handling mail server communication instead of using smtp.SendMail() and thus opening a new connection for each mail

This commit is contained in:
ChaoticByte 2023-10-19 21:01:29 +02:00
parent 8c427cc571
commit e5cc0ccbf1

90
mail.go
View file

@ -3,12 +3,13 @@
package main package main
import ( import (
"crypto/tls"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net/mail" "net/mail"
"net/smtp" "net/smtp"
"slices" "slices"
"strings" "time"
) )
const MAIL_LINE_SEP = "\r\n" const MAIL_LINE_SEP = "\r\n"
@ -22,13 +23,7 @@ func (c MailContent) serializeValidMail(from string, to string) []byte {
// We'll send base64 encoded Subject & Body, because we Dschörmäns have umlauts // We'll send base64 encoded Subject & Body, because we Dschörmäns have umlauts
// and I'm too lazy to encode ä into =E4 and so on // and I'm too lazy to encode ä into =E4 and so on
subjectEncoded := base64.StdEncoding.EncodeToString([]byte(c.Subject)) subjectEncoded := base64.StdEncoding.EncodeToString([]byte(c.Subject))
bodyEncoded := base64.StdEncoding.EncodeToString( bodyEncoded := base64.StdEncoding.EncodeToString([]byte(c.Body))
[]byte( // ensure that all lines end with CRLF
strings.ReplaceAll(
strings.ReplaceAll(c.Body, "\n", MAIL_LINE_SEP), "\r\r", "\r",
),
),
)
data := []byte(fmt.Sprintf( data := []byte(fmt.Sprintf(
"Content-Type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: base64\r\nFrom: %v%vTo: %v%vSubject: =?utf-8?b?%v?=%v%v%v", "Content-Type: text/plain; charset=\"utf-8\"\r\nContent-Transfer-Encoding: base64\r\nFrom: %v%vTo: %v%vSubject: =?utf-8?b?%v?=%v%v%v",
from, MAIL_LINE_SEP, from, MAIL_LINE_SEP,
@ -68,6 +63,7 @@ func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTempla
logger.debug("Generating and sending mails to " + r.Address + " ...") logger.debug("Generating and sending mails to " + r.Address + " ...")
cacheHits := 0 cacheHits := 0
cacheMisses := 0 cacheMisses := 0
mails := [][]byte{}
for _, n := range filteredNotices { for _, n := range filteredNotices {
var data []byte var data []byte
cacheResult := (*cache)[n.Uuid] cacheResult := (*cache)[n.Uuid]
@ -86,20 +82,78 @@ func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTempla
// add to cache // add to cache
(*cache)[n.Uuid] = data (*cache)[n.Uuid] = data
} }
err := smtp.SendMail( mails = append(mails, data)
fmt.Sprintf("%v:%v", smtpConfig.ServerHost, smtpConfig.ServerPort), }
auth, logger.debug(fmt.Sprintf("%v mail cache hits, %v misses", cacheHits, cacheMisses))
smtpConfig.From, err := sendMails(
[]string{r.Address}, smtpConfig,
data, auth,
) r.Address,
mails,
)
if err != nil {
return err
}
logger.debug("Successfully sent all mails to " + r.Address)
return nil
}
func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, data [][]byte) error {
addr := fmt.Sprintf("%v:%v", smtpConf.ServerHost, smtpConf.ServerPort)
logger.debug("Connecting to mail server at " + addr + " ...")
connection, err := smtp.Dial(addr)
if err != nil {
return err
}
defer connection.Close()
// can leave out connection.Hello
hasTlsExt, _ := connection.Extension("starttls")
if hasTlsExt {
err = connection.StartTLS(&tls.Config{ServerName: smtpConf.ServerHost})
if err != nil { if err != nil {
return err return err
} }
logger.debug("Mail Server supports TLS")
} else {
logger.debug("Mail Server doesn't support TLS")
} }
logger.debug(fmt.Sprintf("%v mail cache hits, %v misses", cacheHits, cacheMisses)) logger.debug("Authenticating to mail server ...")
logger.debug("Successfully sent all mails to " + r.Address) err = connection.Auth(auth)
return nil if err != nil {
return err
}
if logger.LogLevel >= 3 {
fmt.Printf("DEBUG %v Sending mails to server ", time.Now().Format("2006/01/02 15:04:05.000000"))
}
for _, d := range data {
err = connection.Mail(smtpConf.From)
if err != nil {
return err
}
err = connection.Rcpt(to)
if err != nil {
return err
}
writer, err := connection.Data()
if err != nil {
return err
}
_, err = writer.Write(d)
if err != nil {
return err
}
err = writer.Close()
if err != nil {
return err
}
if logger.LogLevel >= 3 {
print(".")
}
}
if logger.LogLevel >= 3 {
print("\n")
}
return connection.Quit()
} }
func mailAddressIsValid(address string) bool { func mailAddressIsValid(address string) bool {