email: replace gomail with go-mail (#8164)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ᴊᴏᴇ ᴄʜᴇɴ
2026-02-10 17:44:01 -05:00
committed by GitHub
parent a1fa62b270
commit 94d6e53dc2
17 changed files with 180 additions and 225 deletions

View File

@@ -28,5 +28,5 @@ This applies to all texts, including but not limited to UI, documentation, code
## Source code control
- When pushing changes to a pull request from a fork, use SSH address and do not add remote.
- Never automatically executes commands that touches Git history even if the session does not require approvals, including but not limited to `rebase`, `commit`, `push`, `pull`, `reset`, `amend`. Exceptions are only allowed case-by-case.
- Do not amend commits unless being explicitly asked to do so.
- Never commit on the `main` branch directly unless being explicitly asked to do so. A single ask only grants a single commit action on the `main` branch.
- Never amend commits unless being explicitly asked to do so.

View File

@@ -11,6 +11,7 @@ All notable changes to Gogs are documented in this file.
### Removed
- The `gogs cert` subcommand. [#8153](https://github.com/gogs/gogs/pull/8153)
- The `[email] DISABLE_HELO` configuration option. HELO/EHLO is now always sent during SMTP handshake. [#8164](https://github.com/gogs/gogs/pull/8164)
## 0.14.1

View File

@@ -197,8 +197,6 @@ USER = noreply@gogs.localhost
; The login password.
PASSWORD =
; Whether to disable HELO operation when the hostname is different.
DISABLE_HELO =
; The custom hostname for HELO operation, default is from system.
HELO_HOSTNAME =

View File

@@ -1262,7 +1262,6 @@ config.email.subject_prefix = Subject prefix
config.email.host = Host
config.email.from = From
config.email.user = User
config.email.disable_helo = Disable HELO
config.email.helo_hostname = HELO hostname
config.email.skip_verify = Skip certificate verify
config.email.use_certificate = Use custom certificate

3
go.mod
View File

@@ -43,11 +43,11 @@ require (
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e
github.com/urfave/cli/v3 v3.6.2
github.com/wneessen/go-mail v0.7.2
golang.org/x/crypto v0.47.0
golang.org/x/image v0.35.0
golang.org/x/net v0.48.0
golang.org/x/text v0.33.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
gopkg.in/macaron.v1 v1.5.1
gorm.io/driver/mysql v1.5.2
@@ -132,7 +132,6 @@ require (
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect
gopkg.in/redis.v2 v2.3.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

6
go.sum
View File

@@ -433,6 +433,8 @@ github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e h1:Qf3QQl/zmEbWD
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0=
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
@@ -589,8 +591,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
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/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -599,8 +599,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
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/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=

View File

@@ -56,7 +56,6 @@ var (
User string
Password string
DisableHELO bool `ini:"DISABLE_HELO"`
HELOHostname string `ini:"HELO_HOSTNAME"`
SkipVerify bool

View File

@@ -88,7 +88,6 @@ HOST=smtp.mailgun.org:587
FROM=noreply@gogs.localhost
USER=noreply@gogs.localhost
PASSWORD=87654321
DISABLE_HELO=false
HELO_HOSTNAME=
SKIP_VERIFY=false
USE_CERTIFICATE=false

View File

@@ -151,7 +151,9 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
names = append(names, issue.Assignee.Name)
}
}
email.SendIssueCommentMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos)
if err = email.SendIssueCommentMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos); err != nil {
return errors.Wrap(err, "send issue comment mail")
}
// Mail mentioned people and exclude watchers.
names = append(names, doer.Name)
@@ -168,7 +170,9 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
if err != nil {
return errors.Wrap(err, "get mailable emails by usernames")
}
email.SendIssueMentionMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos)
if err = email.SendIssueMentionMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos); err != nil {
return errors.Wrap(err, "send issue mention mail")
}
return nil
}

View File

@@ -3,13 +3,13 @@ package email
import (
"fmt"
"html/template"
"net/mail"
"path/filepath"
"sync"
"time"
"gopkg.in/gomail.v2"
"github.com/cockroachdb/errors"
"gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/markup"
@@ -72,7 +72,11 @@ func render(tpl string, data map[string]any) (string, error) {
}
func SendTestMail(email string) error {
return gomail.Send(&Sender{}, NewMessage([]string{email}, "Gogs Test Email", "Hello 👋, greeting from Gogs!").Message)
msg, err := newMessage([]string{email}, "Gogs Test Email", "Hello 👋, greeting from Gogs!")
if err != nil {
return errors.Wrap(err, "new message")
}
return sendMessage(msg)
}
/*
@@ -98,7 +102,7 @@ type Issue interface {
HTMLURL() string
}
func SendUserMail(_ *macaron.Context, u User, tpl, code, subject, info string) {
func SendUserMail(_ *macaron.Context, u User, tpl, code, subject, info string) error {
data := map[string]any{
"Username": u.DisplayName(),
"ActiveCodeLives": conf.Auth.ActivateCodeLives / 60,
@@ -107,26 +111,28 @@ func SendUserMail(_ *macaron.Context, u User, tpl, code, subject, info string) {
}
body, err := render(tpl, data)
if err != nil {
log.Error("render: %v", err)
return
return errors.Wrap(err, "render")
}
msg := NewMessage([]string{u.Email()}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, %s", u.ID(), info)
msg, err := newMessage([]string{u.Email()}, subject, body)
if err != nil {
return errors.Wrap(err, "new message")
}
msg.info = fmt.Sprintf("UID: %d, %s", u.ID(), info)
Send(msg)
send(msg)
return nil
}
func SendActivateAccountMail(c *macaron.Context, u User) {
SendUserMail(c, u, tmplAuthActivate, u.GenerateEmailActivateCode(u.Email()), c.Tr("mail.activate_account"), "activate account")
func SendActivateAccountMail(c *macaron.Context, u User) error {
return SendUserMail(c, u, tmplAuthActivate, u.GenerateEmailActivateCode(u.Email()), c.Tr("mail.activate_account"), "activate account")
}
func SendResetPasswordMail(c *macaron.Context, u User) {
SendUserMail(c, u, tmplAuthResetPassword, u.GenerateEmailActivateCode(u.Email()), c.Tr("mail.reset_password"), "reset password")
func SendResetPasswordMail(c *macaron.Context, u User) error {
return SendUserMail(c, u, tmplAuthResetPassword, u.GenerateEmailActivateCode(u.Email()), c.Tr("mail.reset_password"), "reset password")
}
// SendActivateAccountMail sends confirmation email.
func SendActivateEmailMail(c *macaron.Context, u User, email string) {
func SendActivateEmailMail(c *macaron.Context, u User, email string) error {
data := map[string]any{
"Username": u.DisplayName(),
"ActiveCodeLives": conf.Auth.ActivateCodeLives / 60,
@@ -135,35 +141,39 @@ func SendActivateEmailMail(c *macaron.Context, u User, email string) {
}
body, err := render(tmplAuthActivateEmail, data)
if err != nil {
log.Error("HTMLString: %v", err)
return
return errors.Wrap(err, "render")
}
msg := NewMessage([]string{email}, c.Tr("mail.activate_email"), body)
msg.Info = fmt.Sprintf("UID: %d, activate email", u.ID())
msg, err := newMessage([]string{email}, c.Tr("mail.activate_email"), body)
if err != nil {
return errors.Wrap(err, "new message")
}
msg.info = fmt.Sprintf("UID: %d, activate email", u.ID())
Send(msg)
send(msg)
return nil
}
// SendRegisterNotifyMail triggers a notify e-mail by admin created a account.
func SendRegisterNotifyMail(c *macaron.Context, u User) {
func SendRegisterNotifyMail(c *macaron.Context, u User) error {
data := map[string]any{
"Username": u.DisplayName(),
}
body, err := render(tmplAuthRegisterNotify, data)
if err != nil {
log.Error("HTMLString: %v", err)
return
return errors.Wrap(err, "render")
}
msg := NewMessage([]string{u.Email()}, c.Tr("mail.register_notify"), body)
msg.Info = fmt.Sprintf("UID: %d, registration notify", u.ID())
msg, err := newMessage([]string{u.Email()}, c.Tr("mail.register_notify"), body)
if err != nil {
return errors.Wrap(err, "new message")
}
msg.info = fmt.Sprintf("UID: %d, registration notify", u.ID())
Send(msg)
send(msg)
return nil
}
// SendCollaboratorMail sends mail notification to new collaborator.
func SendCollaboratorMail(u, doer User, repo Repository) {
func SendCollaboratorMail(u, doer User, repo Repository) error {
subject := fmt.Sprintf("%s added you to %s", doer.DisplayName(), repo.FullName())
data := map[string]any{
@@ -173,14 +183,17 @@ func SendCollaboratorMail(u, doer User, repo Repository) {
}
body, err := render(tmplNotifyCollaborator, data)
if err != nil {
log.Error("HTMLString: %v", err)
return
return errors.Wrap(err, "render")
}
msg := NewMessage([]string{u.Email()}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, add collaborator", u.ID())
msg, err := newMessage([]string{u.Email()}, subject, body)
if err != nil {
return errors.Wrap(err, "new message")
}
msg.info = fmt.Sprintf("UID: %d, add collaborator", u.ID())
Send(msg)
send(msg)
return nil
}
func composeTplData(subject, body, link string) map[string]any {
@@ -191,34 +204,47 @@ func composeTplData(subject, body, link string) map[string]any {
return data
}
func composeIssueMessage(issue Issue, repo Repository, doer User, tplName string, tos []string, info string) *Message {
func composeIssueMessage(issue Issue, repo Repository, doer User, tplName string, tos []string, info string) (*message, error) {
subject := issue.MailSubject()
body := string(markup.Markdown([]byte(issue.Content()), repo.HTMLURL(), repo.ComposeMetas()))
data := composeTplData(subject, body, issue.HTMLURL())
data["Doer"] = doer
content, err := render(tplName, data)
if err != nil {
log.Error("HTMLString (%s): %v", tplName, err)
return nil, errors.Wrapf(err, "render %q", tplName)
}
from := gomail.NewMessage().FormatAddress(conf.Email.FromEmail, doer.DisplayName())
msg := NewMessageFrom(tos, from, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, %s", subject, info)
return msg
from := (&mail.Address{Name: doer.DisplayName(), Address: conf.Email.FromEmail}).String()
msg, err := newMessageFrom(tos, from, subject, content)
if err != nil {
return nil, errors.Wrap(err, "new message")
}
msg.info = fmt.Sprintf("Subject: %s, %s", subject, info)
return msg, nil
}
// SendIssueCommentMail composes and sends issue comment emails to target receivers.
func SendIssueCommentMail(issue Issue, repo Repository, doer User, tos []string) {
func SendIssueCommentMail(issue Issue, repo Repository, doer User, tos []string) error {
if len(tos) == 0 {
return
return nil
}
Send(composeIssueMessage(issue, repo, doer, tmplIssueComment, tos, "issue comment"))
msg, err := composeIssueMessage(issue, repo, doer, tmplIssueComment, tos, "issue comment")
if err != nil {
return errors.Wrap(err, "compose issue message")
}
send(msg)
return nil
}
// SendIssueMentionMail composes and sends issue mention emails to target receivers.
func SendIssueMentionMail(issue Issue, repo Repository, doer User, tos []string) {
func SendIssueMentionMail(issue Issue, repo Repository, doer User, tos []string) error {
if len(tos) == 0 {
return
return nil
}
Send(composeIssueMessage(issue, repo, doer, tmplIssueMention, tos, "issue mention"))
msg, err := composeIssueMessage(issue, repo, doer, tmplIssueMention, tos, "issue mention")
if err != nil {
return errors.Wrap(err, "compose issue message")
}
send(msg)
return nil
}

View File

@@ -2,214 +2,134 @@ package email
import (
"crypto/tls"
"io"
"net"
"net/smtp"
"os"
"strconv"
"strings"
"time"
"github.com/cockroachdb/errors"
"github.com/inbucket/html2text"
"gopkg.in/gomail.v2"
gomail "github.com/wneessen/go-mail"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
)
type Message struct {
Info string // Message information for log purpose.
*gomail.Message
type message struct {
info string
msg *gomail.Msg
confirmChan chan struct{}
}
// NewMessageFrom creates new mail message object with custom From header.
func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
func newMessageFrom(to []string, from, subject, htmlBody string) (*message, error) {
log.Trace("NewMessageFrom (htmlBody):\n%s", htmlBody)
msg := gomail.NewMessage()
msg.SetHeader("From", from)
msg.SetHeader("To", to...)
msg.SetHeader("Subject", conf.Email.SubjectPrefix+subject)
msg.SetDateHeader("Date", time.Now())
m := gomail.NewMsg()
if err := m.From(from); err != nil {
return nil, errors.Wrapf(err, "set From address %q", from)
}
if err := m.To(to...); err != nil {
return nil, errors.Wrap(err, "set To addresses")
}
m.Subject(conf.Email.SubjectPrefix + subject)
m.SetDate()
contentType := "text/html"
body := htmlBody
switchedToPlaintext := false
if conf.Email.UsePlainText || conf.Email.AddPlainTextAlt {
plainBody, err := html2text.FromString(htmlBody)
if err != nil {
log.Error("html2text.FromString: %v", err)
return nil, errors.Wrap(err, "convert HTML to plain text")
}
if conf.Email.UsePlainText {
m.SetBodyString(gomail.TypeTextPlain, plainBody)
} else {
contentType = "text/plain"
body = plainBody
switchedToPlaintext = true
m.SetBodyString(gomail.TypeTextPlain, plainBody)
m.AddAlternativeString(gomail.TypeTextHTML, htmlBody)
}
} else {
m.SetBodyString(gomail.TypeTextHTML, htmlBody)
}
msg.SetBody(contentType, body)
if switchedToPlaintext && conf.Email.AddPlainTextAlt && !conf.Email.UsePlainText {
// The AddAlternative method name is confusing - adding html as an "alternative" will actually cause mail
// clients to show it as first priority, and the text "main body" is the 2nd priority fallback.
// See: https://godoc.org/gopkg.in/gomail.v2#Message.AddAlternative
msg.AddAlternative("text/html", htmlBody)
}
return &Message{
Message: msg,
return &message{
msg: m,
confirmChan: make(chan struct{}),
}
}, nil
}
// NewMessage creates new mail message object with default From header.
func NewMessage(to []string, subject, body string) *Message {
return NewMessageFrom(to, conf.Email.From, subject, body)
func newMessage(to []string, subject, body string) (*message, error) {
return newMessageFrom(to, conf.Email.From, subject, body)
}
type loginAuth struct {
username, password string
}
// SMTP AUTH LOGIN Auth Handler
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (*loginAuth) Start(_ *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.Newf("unknwon fromServer: %s", string(fromServer))
}
}
return nil, nil
}
type Sender struct{}
func (*Sender) Send(from string, to []string, msg io.WriterTo) error {
func newSMTPClient() (*gomail.Client, error) {
opts := conf.Email
host, port, err := net.SplitHostPort(opts.Host)
host, portStr, err := net.SplitHostPort(opts.Host)
if err != nil {
return err
return nil, err
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, err
}
clientOpts := []gomail.Option{
gomail.WithPort(port),
}
if port == 465 {
clientOpts = append(clientOpts, gomail.WithSSL())
} else {
clientOpts = append(clientOpts, gomail.WithTLSPolicy(gomail.TLSOpportunistic))
}
if opts.HELOHostname != "" {
clientOpts = append(clientOpts, gomail.WithHELO(opts.HELOHostname))
}
tlsconfig := &tls.Config{
InsecureSkipVerify: opts.SkipVerify,
ServerName: host,
}
if opts.UseCertificate {
cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
if err != nil {
return err
return nil, err
}
tlsconfig.Certificates = []tls.Certificate{cert}
}
clientOpts = append(clientOpts, gomail.WithTLSConfig(tlsconfig))
conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
if len(opts.User) > 0 {
clientOpts = append(clientOpts,
gomail.WithSMTPAuth(gomail.SMTPAuthAutoDiscover),
gomail.WithUsername(opts.User),
gomail.WithPassword(opts.Password),
)
}
return gomail.NewClient(host, clientOpts...)
}
func sendMessage(msg *message) error {
client, err := newSMTPClient()
if err != nil {
return err
}
defer conn.Close()
isSecureConn := false
// Start TLS directly if the port ends with 465 (SMTPS protocol)
if strings.HasSuffix(port, "465") {
conn = tls.Client(conn, tlsconfig)
isSecureConn = true
}
client, err := smtp.NewClient(conn, host)
if err != nil {
return errors.Newf("NewClient: %v", err)
}
if !opts.DisableHELO {
hostname := opts.HELOHostname
if hostname == "" {
hostname, err = os.Hostname()
if err != nil {
return err
}
}
if err = client.Hello(hostname); err != nil {
return errors.Newf("hello: %v", err)
}
}
// If not using SMTPS, always use STARTTLS if available
hasStartTLS, _ := client.Extension("STARTTLS")
if !isSecureConn && hasStartTLS {
if err = client.StartTLS(tlsconfig); err != nil {
return errors.Newf("StartTLS: %v", err)
}
}
canAuth, options := client.Extension("AUTH")
if canAuth && len(opts.User) > 0 {
var auth smtp.Auth
if strings.Contains(options, "CRAM-MD5") {
auth = smtp.CRAMMD5Auth(opts.User, opts.Password)
} else if strings.Contains(options, "PLAIN") {
auth = smtp.PlainAuth("", opts.User, opts.Password, host)
} else if strings.Contains(options, "LOGIN") {
// Patch for AUTH LOGIN
auth = LoginAuth(opts.User, opts.Password)
}
if auth != nil {
if err = client.Auth(auth); err != nil {
return errors.Newf("auth: %v", err)
}
}
}
if err = client.Mail(from); err != nil {
return errors.Newf("mail: %v", err)
}
for _, rec := range to {
if err = client.Rcpt(rec); err != nil {
return errors.Newf("rcpt: %v", err)
}
}
w, err := client.Data()
if err != nil {
return errors.Newf("data: %v", err)
} else if _, err = msg.WriteTo(w); err != nil {
return errors.Newf("write to: %v", err)
} else if err = w.Close(); err != nil {
return errors.Newf("close: %v", err)
}
return client.Quit()
return client.DialAndSend(msg.msg)
}
func processMailQueue() {
sender := &Sender{}
for msg := range mailQueue {
log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
if err := gomail.Send(sender, msg.Message); err != nil {
log.Error("Failed to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
to := strings.Join(msg.msg.GetToString(), ", ")
log.Trace("New e-mail sending request %s: %s", to, msg.info)
if err := sendMessage(msg); err != nil {
log.Error("Failed to send emails %s: %s - %v", to, msg.info, err)
} else {
log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
log.Trace("E-mails sent %s: %s", to, msg.info)
}
msg.confirmChan <- struct{}{}
}
}
var mailQueue chan *Message
var mailQueue chan *message
// NewContext initializes settings for mailer.
func NewContext() {
@@ -220,14 +140,14 @@ func NewContext() {
return
}
mailQueue = make(chan *Message, 1000)
mailQueue = make(chan *message, 1000)
go processMailQueue()
}
// Send puts new message object into mail queue.
// send puts new message object into mail queue.
// It returns without confirmation (mail processed asynchronously) in normal cases,
// but waits/blocks under hook mode to make sure mail has been sent.
func Send(msg *Message) {
func send(msg *message) {
if !conf.Email.Enabled {
return
}

View File

@@ -106,7 +106,9 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
// Send email notification.
if f.SendNotify && conf.Email.Enabled {
email.SendRegisterNotifyMail(c.Context, database.NewMailerUser(user))
if err := email.SendRegisterNotifyMail(c.Context, database.NewMailerUser(user)); err != nil {
log.Error("Failed to send register notify mail: %v", err)
}
}
c.Flash.Success(c.Tr("admin.users.new_success", user.Name))

View File

@@ -69,7 +69,9 @@ func adminCreateUser(c *context.APIContext, form adminCreateUserRequest) {
// Send email notification.
if form.SendNotify && conf.Email.Enabled {
email.SendRegisterNotifyMail(c.Context.Context, database.NewMailerUser(u))
if err := email.SendRegisterNotifyMail(c.Context.Context, database.NewMailerUser(u)); err != nil {
log.Error("Failed to send register notify mail: %v", err)
}
}
c.JSON(http.StatusCreated, toUser(u))

View File

@@ -401,7 +401,9 @@ func SettingsCollaborationPost(c *context.Context) {
}
if conf.User.EnableEmailNotification {
email.SendCollaboratorMail(database.NewMailerUser(u), database.NewMailerUser(c.User), database.NewMailerRepo(c.Repo.Repository))
if err := email.SendCollaboratorMail(database.NewMailerUser(u), database.NewMailerUser(c.User), database.NewMailerRepo(c.Repo.Repository)); err != nil {
log.Error("Failed to send collaborator mail: %v", err)
}
}
c.Flash.Success(c.Tr("repo.settings.add_collaborator_success"))

View File

@@ -385,7 +385,9 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
// Send confirmation email.
if conf.Auth.RequireEmailConfirmation && user.ID > 1 {
email.SendActivateAccountMail(c.Context, database.NewMailerUser(user))
if err := email.SendActivateAccountMail(c.Context, database.NewMailerUser(user)); err != nil {
log.Error("Failed to send activate account mail: %v", err)
}
c.Data["IsSendRegisterMail"] = true
c.Data["Email"] = user.Email
c.Data["Hours"] = conf.Auth.ActivateCodeLives / 60
@@ -469,7 +471,9 @@ func Activate(c *context.Context) {
c.Data["ResendLimited"] = true
} else {
c.Data["Hours"] = conf.Auth.ActivateCodeLives / 60
email.SendActivateAccountMail(c.Context, database.NewMailerUser(c.User))
if err := email.SendActivateAccountMail(c.Context, database.NewMailerUser(c.User)); err != nil {
log.Error("Failed to send activate account mail: %v", err)
}
if err := c.Cache.Put(userutil.MailResendCacheKey(c.User.ID), 1, 180); err != nil {
log.Error("Failed to put cache key 'mail resend': %v", err)
@@ -579,7 +583,9 @@ func ForgotPasswdPost(c *context.Context) {
return
}
email.SendResetPasswordMail(c.Context, database.NewMailerUser(u))
if err = email.SendResetPasswordMail(c.Context, database.NewMailerUser(u)); err != nil {
log.Error("Failed to send reset password mail: %v", err)
}
if err = c.Cache.Put(userutil.MailResendCacheKey(u.ID), 1, 180); err != nil {
log.Error("Failed to put cache key 'mail resend': %v", err)
}

View File

@@ -280,7 +280,9 @@ func SettingsEmailPost(c *context.Context, f form.AddEmail) {
// Send confirmation email
if conf.Auth.RequireEmailConfirmation {
email.SendActivateEmailMail(c.Context, database.NewMailerUser(c.User), f.Email)
if err := email.SendActivateEmailMail(c.Context, database.NewMailerUser(c.User), f.Email); err != nil {
log.Error("Failed to send activate email mail: %v", err)
}
if err := c.Cache.Put("MailResendLimit_"+c.User.LowerName, c.User.LowerName, 180); err != nil {
log.Error("Set cache 'MailResendLimit' failed: %v", err)

View File

@@ -237,8 +237,6 @@
<div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.email.disable_helo"}}</dt>
<dd><i class="fa fa{{if .Email.DisableHELO}}-check{{end}}-square-o"></i></dd>
<dt>{{.i18n.Tr "admin.config.email.helo_hostname"}}</dt>
<dd>
{{if .Email.HELOHostname}}