Add option to change mail from user display name (#31528)

Make it posible to let mails show e.g.:

`Max Musternam (via gitea.kithara.com) <gitea@kithara.com>`

Docs: https://gitea.com/gitea/docs/pulls/23

---
*Sponsored by Kithara Software GmbH*
This commit is contained in:
6543 2024-07-14 14:27:00 -07:00 committed by GitHub
parent 0d08bb6112
commit 0f53324182
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 86 additions and 3 deletions

View File

@ -1676,6 +1676,10 @@ LEVEL = Info
;; Sometimes it is helpful to use a different address on the envelope. Set this to use ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address. ;; Sometimes it is helpful to use a different address on the envelope. Set this to use ENVELOPE_FROM as the from on the envelope. Set to `<>` to send an empty address.
;ENVELOPE_FROM = ;ENVELOPE_FROM =
;; ;;
;; If gitea sends mails on behave of users, it will just use the name also displayed in the WebUI. If you want e.g. `Mister X (by CodeIt) <gitea@codeit.net>`,
;; set it to `{{ .DisplayName }} (by {{ .AppName }})`. Available Variables: `.DisplayName`, `.AppName` and `.Domain`.
;FROM_DISPLAY_NAME_FORMAT = {{ .DisplayName }}
;;
;; Mailer user name and password, if required by provider. ;; Mailer user name and password, if required by provider.
;USER = ;USER =
;; ;;

View File

@ -8,6 +8,7 @@ import (
"net" "net"
"net/mail" "net/mail"
"strings" "strings"
"text/template"
"time" "time"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -46,6 +47,10 @@ type Mailer struct {
SendmailArgs []string `ini:"-"` SendmailArgs []string `ini:"-"`
SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"` SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"`
SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"` SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"`
// Customization
FromDisplayNameFormat string `ini:"FROM_DISPLAY_NAME_FORMAT"`
FromDisplayNameFormatTemplate *template.Template `ini:"-"`
} }
// MailService the global mailer // MailService the global mailer
@ -226,6 +231,16 @@ func loadMailerFrom(rootCfg ConfigProvider) {
log.Error("no mailer.FROM provided, email system may not work.") log.Error("no mailer.FROM provided, email system may not work.")
} }
MailService.FromDisplayNameFormatTemplate, _ = template.New("mailFrom").Parse("{{ .DisplayName }}")
if MailService.FromDisplayNameFormat != "" {
template, err := template.New("mailFrom").Parse(MailService.FromDisplayNameFormat)
if err != nil {
log.Error("mailer.FROM_DISPLAY_NAME_FORMAT is no valid template: %v", err)
} else {
MailService.FromDisplayNameFormatTemplate = template
}
}
switch MailService.EnvelopeFrom { switch MailService.EnvelopeFrom {
case "": case "":
MailService.OverrideEnvelopeFrom = false MailService.OverrideEnvelopeFrom = false

View File

@ -314,7 +314,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
for _, recipient := range recipients { for _, recipient := range recipients {
msg := NewMessageFrom( msg := NewMessageFrom(
recipient.Email, recipient.Email,
ctx.Doer.GetCompleteName(), fromDisplayName(ctx.Doer),
setting.MailService.FromEmail, setting.MailService.FromEmail,
subject, subject,
mailBody.String(), mailBody.String(),
@ -536,3 +536,19 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act
} }
return typeName, name, template return typeName, name, template
} }
func fromDisplayName(u *user_model.User) string {
if setting.MailService.FromDisplayNameFormatTemplate != nil {
var ctx bytes.Buffer
err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{
"DisplayName": u.DisplayName(),
"AppName": setting.AppName,
"Domain": setting.Domain,
})
if err == nil {
return mime.QEncoding.Encode("utf-8", ctx.String())
}
log.Error("fromDisplayName: %w", err)
}
return u.GetCompleteName()
}

View File

@ -86,7 +86,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re
} }
msgs := make([]*Message, 0, len(tos)) msgs := make([]*Message, 0, len(tos))
publisherName := rel.Publisher.DisplayName() publisherName := fromDisplayName(rel.Publisher)
msgID := generateMessageIDForRelease(rel) msgID := generateMessageIDForRelease(rel)
for _, to := range tos { for _, to := range tos {
msg := NewMessageFrom(to.EmailTo(), publisherName, setting.MailService.FromEmail, subject, mailBody.String()) msg := NewMessageFrom(to.EmailTo(), publisherName, setting.MailService.FromEmail, subject, mailBody.String())

View File

@ -79,7 +79,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
} }
for _, to := range emailTos { for _, to := range emailTos {
msg := NewMessage(to.EmailTo(), subject, content.String()) msg := NewMessageFrom(to.EmailTo(), fromDisplayName(doer), setting.MailService.FromEmail, subject, content.String())
msg.Info = fmt.Sprintf("UID: %d, repository pending transfer notification", newOwner.ID) msg.Info = fmt.Sprintf("UID: %d, repository pending transfer notification", newOwner.ID)
SendAsync(msg) SendAsync(msg)

View File

@ -403,3 +403,51 @@ func TestGenerateMessageIDForRelease(t *testing.T) {
}) })
assert.Equal(t, "<owner/repo/releases/1@localhost>", msgID) assert.Equal(t, "<owner/repo/releases/1@localhost>", msgID)
} }
func TestFromDisplayName(t *testing.T) {
template, err := texttmpl.New("mailFrom").Parse("{{ .DisplayName }}")
assert.NoError(t, err)
setting.MailService = &setting.Mailer{FromDisplayNameFormatTemplate: template}
defer func() { setting.MailService = nil }()
tests := []struct {
userDisplayName string
fromDisplayName string
}{{
userDisplayName: "test",
fromDisplayName: "test",
}, {
userDisplayName: "Hi Its <Mee>",
fromDisplayName: "Hi Its <Mee>",
}, {
userDisplayName: "Æsir",
fromDisplayName: "=?utf-8?q?=C3=86sir?=",
}, {
userDisplayName: "new😀user",
fromDisplayName: "=?utf-8?q?new=F0=9F=98=80user?=",
}}
for _, tc := range tests {
t.Run(tc.userDisplayName, func(t *testing.T) {
user := &user_model.User{FullName: tc.userDisplayName, Name: "tmp"}
got := fromDisplayName(user)
assert.EqualValues(t, tc.fromDisplayName, got)
})
}
t.Run("template with all available vars", func(t *testing.T) {
template, err = texttmpl.New("mailFrom").Parse("{{ .DisplayName }} (by {{ .AppName }} on [{{ .Domain }}])")
assert.NoError(t, err)
setting.MailService = &setting.Mailer{FromDisplayNameFormatTemplate: template}
oldAppName := setting.AppName
setting.AppName = "Code IT"
oldDomain := setting.Domain
setting.Domain = "code.it"
defer func() {
setting.AppName = oldAppName
setting.Domain = oldDomain
}()
assert.EqualValues(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"}))
})
}