Send emails with gomail to support SMTP servers with AUTH

This commit is contained in:
Paul Harris 2025-07-02 15:51:26 +08:00
parent 30301c8a7f
commit 66316eae00
4 changed files with 76 additions and 77 deletions

2
go.mod
View file

@ -100,5 +100,7 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/grpc v1.72.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

4
go.sum
View file

@ -275,9 +275,13 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -4,9 +4,10 @@ import (
_ "embed" // required by go:embed
"encoding/json"
"fmt"
gomail "gopkg.in/gomail.v2"
"mime"
"net"
"net/smtp"
"strconv"
"strings"
"sync"
"time"
@ -29,17 +30,17 @@ type smtpSender struct {
func (s *smtpSender) Send(v *visitor, m *message, to string) error {
return s.withCount(v, m, func() error {
host, _, err := net.SplitHostPort(s.config.SMTPSenderAddr)
host, portStr, err := net.SplitHostPort(s.config.SMTPSenderAddr)
if err != nil {
return err
}
message, err := formatMail(s.config.BaseURL, v.ip.String(), s.config.SMTPSenderFrom, to, m)
port, err := strconv.Atoi(portStr)
if err != nil {
return err
}
var auth smtp.Auth
if s.config.SMTPSenderUser != "" {
auth = smtp.PlainAuth("", s.config.SMTPSenderUser, s.config.SMTPSenderPass, host)
from, subject, message, err := formatMail(s.config.BaseURL, v.ip.String(), s.config.SMTPSenderFrom, m)
if err != nil {
return err
}
ev := logvm(v, m).
Tag(tagEmail).
@ -53,7 +54,18 @@ func (s *smtpSender) Send(v *visitor, m *message, to string) error {
} else if ev.IsDebug() {
ev.Debug("Sending email")
}
return smtp.SendMail(s.config.SMTPSenderAddr, auth, s.config.SMTPSenderFrom, []string{to}, []byte(message))
smtpMessage := gomail.NewMessage()
smtpMessage.SetHeader("From", from)
smtpMessage.SetHeader("To", to)
smtpMessage.SetHeader("Subject", subject)
smtpMessage.SetBody("text/plain", message)
dialer := gomail.NewDialer(host, port, s.config.SMTPSenderUser, s.config.SMTPSenderPass)
err = dialer.DialAndSend(smtpMessage)
if err == nil {
ev.Debug("Mail sent ok")
}
return err
})
}
@ -76,7 +88,8 @@ func (s *smtpSender) withCount(v *visitor, m *message, fn func() error) error {
return err
}
func formatMail(baseURL, senderIP, from, to string, m *message) (string, error) {
// returns: from, subject, content
func formatMail(baseURL, senderIP, from string, m *message) (string, string, string, error) {
topicURL := baseURL + "/" + m.Topic
subject := m.Title
if subject == "" {
@ -88,7 +101,7 @@ func formatMail(baseURL, senderIP, from, to string, m *message) (string, error)
if len(m.Tags) > 0 {
emojis, tags, err := toEmojis(m.Tags)
if err != nil {
return "", err
return "", "", "", err
}
if len(emojis) > 0 {
subject = strings.Join(emojis, " ") + " " + subject
@ -100,7 +113,7 @@ func formatMail(baseURL, senderIP, from, to string, m *message) (string, error)
if m.Priority != 0 && m.Priority != 3 {
priority, err := util.PriorityString(m.Priority)
if err != nil {
return "", err
return "", "", "", err
}
if trailer != "" {
trailer += "\n"
@ -110,28 +123,20 @@ func formatMail(baseURL, senderIP, from, to string, m *message) (string, error)
if trailer != "" {
message += "\n\n" + trailer
}
date := time.Unix(m.Time, 0).UTC().Format(time.RFC1123Z)
subject = mime.BEncoding.Encode("utf-8", subject)
body := `From: "{shortTopicURL}" <{from}>
To: {to}
Date: {date}
Subject: {subject}
Content-Type: text/plain; charset="utf-8"
fullFrom := `"{shortTopicURL}" <{from}>`
fullFrom = strings.ReplaceAll(fullFrom, "{from}", from)
fullFrom = strings.ReplaceAll(fullFrom, "{shortTopicURL}", util.ShortTopicURL(topicURL))
{message}
body := `{message}
--
This message was sent by {ip} at {time} via {topicURL}`
body = strings.ReplaceAll(body, "{from}", from)
body = strings.ReplaceAll(body, "{to}", to)
body = strings.ReplaceAll(body, "{date}", date)
body = strings.ReplaceAll(body, "{subject}", subject)
body = strings.ReplaceAll(body, "{message}", message)
body = strings.ReplaceAll(body, "{topicURL}", topicURL)
body = strings.ReplaceAll(body, "{shortTopicURL}", util.ShortTopicURL(topicURL))
body = strings.ReplaceAll(body, "{time}", time.Unix(m.Time, 0).UTC().Format(time.RFC1123))
body = strings.ReplaceAll(body, "{ip}", senderIP)
return body, nil
return fullFrom, subject, body, nil
}
var (

View file

@ -6,28 +6,26 @@ import (
)
func TestFormatMail_Basic(t *testing.T) {
actual, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", "phil@example.com", &message{
actualFrom, actualSubject, actualMessage, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", &message{
ID: "abc",
Time: 1640382204,
Event: "message",
Topic: "alerts",
Message: "A simple message",
})
expected := `From: "ntfy.sh/alerts" <ntfy@ntfy.sh>
To: phil@example.com
Date: Fri, 24 Dec 2021 21:43:24 +0000
Subject: A simple message
Content-Type: text/plain; charset="utf-8"
A simple message
expectedFrom := `"ntfy.sh/alerts" <ntfy@ntfy.sh>`
expectedSubject := `A simple message`
expectedMessage := `A simple message
--
This message was sent by 1.2.3.4 at Fri, 24 Dec 2021 21:43:24 UTC via https://ntfy.sh/alerts`
require.Equal(t, expected, actual)
require.Equal(t, expectedMessage, actualMessage)
require.Equal(t, expectedFrom, actualFrom)
require.Equal(t, expectedSubject, actualSubject)
}
func TestFormatMail_JustEmojis(t *testing.T) {
actual, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", "phil@example.com", &message{
actualFrom, actualSubject, actualMessage, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", &message{
ID: "abc",
Time: 1640382204,
Event: "message",
@ -35,21 +33,19 @@ func TestFormatMail_JustEmojis(t *testing.T) {
Message: "A simple message",
Tags: []string{"grinning"},
})
expected := `From: "ntfy.sh/alerts" <ntfy@ntfy.sh>
To: phil@example.com
Date: Fri, 24 Dec 2021 21:43:24 +0000
Subject: =?utf-8?b?8J+YgCBBIHNpbXBsZSBtZXNzYWdl?=
Content-Type: text/plain; charset="utf-8"
A simple message
expectedFrom := `"ntfy.sh/alerts" <ntfy@ntfy.sh>`
expectedSubject := `=?utf-8?b?8J+YgCBBIHNpbXBsZSBtZXNzYWdl?=`
expectedMessage := `A simple message
--
This message was sent by 1.2.3.4 at Fri, 24 Dec 2021 21:43:24 UTC via https://ntfy.sh/alerts`
require.Equal(t, expected, actual)
require.Equal(t, expectedMessage, actualMessage)
require.Equal(t, expectedFrom, actualFrom)
require.Equal(t, expectedSubject, actualSubject)
}
func TestFormatMail_JustOtherTags(t *testing.T) {
actual, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", "phil@example.com", &message{
actualFrom, actualSubject, actualMessage, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", &message{
ID: "abc",
Time: 1640382204,
Event: "message",
@ -57,23 +53,21 @@ func TestFormatMail_JustOtherTags(t *testing.T) {
Message: "A simple message",
Tags: []string{"not-an-emoji"},
})
expected := `From: "ntfy.sh/alerts" <ntfy@ntfy.sh>
To: phil@example.com
Date: Fri, 24 Dec 2021 21:43:24 +0000
Subject: A simple message
Content-Type: text/plain; charset="utf-8"
A simple message
expectedFrom := `"ntfy.sh/alerts" <ntfy@ntfy.sh>`
expectedSubject := `A simple message`
expectedMessage := `A simple message
Tags: not-an-emoji
--
This message was sent by 1.2.3.4 at Fri, 24 Dec 2021 21:43:24 UTC via https://ntfy.sh/alerts`
require.Equal(t, expected, actual)
require.Equal(t, expectedMessage, actualMessage)
require.Equal(t, expectedFrom, actualFrom)
require.Equal(t, expectedSubject, actualSubject)
}
func TestFormatMail_JustPriority(t *testing.T) {
actual, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", "phil@example.com", &message{
actualFrom, actualSubject, actualMessage, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", &message{
ID: "abc",
Time: 1640382204,
Event: "message",
@ -81,23 +75,21 @@ func TestFormatMail_JustPriority(t *testing.T) {
Message: "A simple message",
Priority: 2,
})
expected := `From: "ntfy.sh/alerts" <ntfy@ntfy.sh>
To: phil@example.com
Date: Fri, 24 Dec 2021 21:43:24 +0000
Subject: A simple message
Content-Type: text/plain; charset="utf-8"
A simple message
expectedFrom := `"ntfy.sh/alerts" <ntfy@ntfy.sh>`
expectedSubject := `A simple message`
expectedMessage := `A simple message
Priority: low
--
This message was sent by 1.2.3.4 at Fri, 24 Dec 2021 21:43:24 UTC via https://ntfy.sh/alerts`
require.Equal(t, expected, actual)
require.Equal(t, expectedMessage, actualMessage)
require.Equal(t, expectedFrom, actualFrom)
require.Equal(t, expectedSubject, actualSubject)
}
func TestFormatMail_UTF8Subject(t *testing.T) {
actual, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", "phil@example.com", &message{
actualFrom, actualSubject, actualMessage, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", &message{
ID: "abc",
Time: 1640382204,
Event: "message",
@ -105,21 +97,19 @@ func TestFormatMail_UTF8Subject(t *testing.T) {
Message: "A simple message",
Title: " :: A not so simple title öäüß ¡Hola, señor!",
})
expected := `From: "ntfy.sh/alerts" <ntfy@ntfy.sh>
To: phil@example.com
Date: Fri, 24 Dec 2021 21:43:24 +0000
Subject: =?utf-8?b?IDo6IEEgbm90IHNvIHNpbXBsZSB0aXRsZSDDtsOkw7zDnyDCoUhvbGEsIHNl?= =?utf-8?b?w7FvciE=?=
Content-Type: text/plain; charset="utf-8"
A simple message
expectedFrom := `"ntfy.sh/alerts" <ntfy@ntfy.sh>`
expectedSubject := `=?utf-8?b?IDo6IEEgbm90IHNvIHNpbXBsZSB0aXRsZSDDtsOkw7zDnyDCoUhvbGEsIHNl?= =?utf-8?b?w7FvciE=?=`
expectedMessage := `A simple message
--
This message was sent by 1.2.3.4 at Fri, 24 Dec 2021 21:43:24 UTC via https://ntfy.sh/alerts`
require.Equal(t, expected, actual)
require.Equal(t, expectedMessage, actualMessage)
require.Equal(t, expectedFrom, actualFrom)
require.Equal(t, expectedSubject, actualSubject)
}
func TestFormatMail_WithAllTheThings(t *testing.T) {
actual, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", "phil@example.com", &message{
actualFrom, actualSubject, actualMessage, _ := formatMail("https://ntfy.sh", "1.2.3.4", "ntfy@ntfy.sh", &message{
ID: "abc",
Time: 1640382204,
Event: "message",
@ -129,13 +119,9 @@ func TestFormatMail_WithAllTheThings(t *testing.T) {
Title: "Oh no 🙈\nThis is a message across\nmultiple lines",
Message: "A message that contains monkeys 🙉\nNo really, though. Monkeys!",
})
expected := `From: "ntfy.sh/alerts" <ntfy@ntfy.sh>
To: phil@example.com
Date: Fri, 24 Dec 2021 21:43:24 +0000
Subject: =?utf-8?b?4pqg77iPIPCfkoAgT2ggbm8g8J+ZiCBUaGlzIGlzIGEgbWVzc2FnZSBhY3Jv?= =?utf-8?b?c3MgbXVsdGlwbGUgbGluZXM=?=
Content-Type: text/plain; charset="utf-8"
A message that contains monkeys 🙉
expectedFrom := `"ntfy.sh/alerts" <ntfy@ntfy.sh>`
expectedSubject := `=?utf-8?b?4pqg77iPIPCfkoAgT2ggbm8g8J+ZiCBUaGlzIGlzIGEgbWVzc2FnZSBhY3Jv?= =?utf-8?b?c3MgbXVsdGlwbGUgbGluZXM=?=`
expectedMessage := `A message that contains monkeys 🙉
No really, though. Monkeys!
Tags: tag123, other
@ -143,5 +129,7 @@ Priority: max
--
This message was sent by 1.2.3.4 at Fri, 24 Dec 2021 21:43:24 UTC via https://ntfy.sh/alerts`
require.Equal(t, expected, actual)
require.Equal(t, expectedMessage, actualMessage)
require.Equal(t, expectedFrom, actualFrom)
require.Equal(t, expectedSubject, actualSubject)
}