Skip to content

Commit 0f53324

Browse files
authored
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) <[email protected]>` Docs: https://gitea.com/gitea/docs/pulls/23 --- *Sponsored by Kithara Software GmbH*
1 parent 0d08bb6 commit 0f53324

File tree

6 files changed

+86
-3
lines changed

6 files changed

+86
-3
lines changed

custom/conf/app.example.ini

+4
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,10 @@ LEVEL = Info
16761676
;; 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.
16771677
;ENVELOPE_FROM =
16781678
;;
1679+
;; 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) <[email protected]>`,
1680+
;; set it to `{{ .DisplayName }} (by {{ .AppName }})`. Available Variables: `.DisplayName`, `.AppName` and `.Domain`.
1681+
;FROM_DISPLAY_NAME_FORMAT = {{ .DisplayName }}
1682+
;;
16791683
;; Mailer user name and password, if required by provider.
16801684
;USER =
16811685
;;

modules/setting/mailer.go

+15
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net"
99
"net/mail"
1010
"strings"
11+
"text/template"
1112
"time"
1213

1314
"code.gitea.io/gitea/modules/log"
@@ -46,6 +47,10 @@ type Mailer struct {
4647
SendmailArgs []string `ini:"-"`
4748
SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"`
4849
SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"`
50+
51+
// Customization
52+
FromDisplayNameFormat string `ini:"FROM_DISPLAY_NAME_FORMAT"`
53+
FromDisplayNameFormatTemplate *template.Template `ini:"-"`
4954
}
5055

5156
// MailService the global mailer
@@ -226,6 +231,16 @@ func loadMailerFrom(rootCfg ConfigProvider) {
226231
log.Error("no mailer.FROM provided, email system may not work.")
227232
}
228233

234+
MailService.FromDisplayNameFormatTemplate, _ = template.New("mailFrom").Parse("{{ .DisplayName }}")
235+
if MailService.FromDisplayNameFormat != "" {
236+
template, err := template.New("mailFrom").Parse(MailService.FromDisplayNameFormat)
237+
if err != nil {
238+
log.Error("mailer.FROM_DISPLAY_NAME_FORMAT is no valid template: %v", err)
239+
} else {
240+
MailService.FromDisplayNameFormatTemplate = template
241+
}
242+
}
243+
229244
switch MailService.EnvelopeFrom {
230245
case "":
231246
MailService.OverrideEnvelopeFrom = false

services/mailer/mail.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
314314
for _, recipient := range recipients {
315315
msg := NewMessageFrom(
316316
recipient.Email,
317-
ctx.Doer.GetCompleteName(),
317+
fromDisplayName(ctx.Doer),
318318
setting.MailService.FromEmail,
319319
subject,
320320
mailBody.String(),
@@ -536,3 +536,19 @@ func actionToTemplate(issue *issues_model.Issue, actionType activities_model.Act
536536
}
537537
return typeName, name, template
538538
}
539+
540+
func fromDisplayName(u *user_model.User) string {
541+
if setting.MailService.FromDisplayNameFormatTemplate != nil {
542+
var ctx bytes.Buffer
543+
err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{
544+
"DisplayName": u.DisplayName(),
545+
"AppName": setting.AppName,
546+
"Domain": setting.Domain,
547+
})
548+
if err == nil {
549+
return mime.QEncoding.Encode("utf-8", ctx.String())
550+
}
551+
log.Error("fromDisplayName: %w", err)
552+
}
553+
return u.GetCompleteName()
554+
}

services/mailer/mail_release.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []*user_model.User, re
8686
}
8787

8888
msgs := make([]*Message, 0, len(tos))
89-
publisherName := rel.Publisher.DisplayName()
89+
publisherName := fromDisplayName(rel.Publisher)
9090
msgID := generateMessageIDForRelease(rel)
9191
for _, to := range tos {
9292
msg := NewMessageFrom(to.EmailTo(), publisherName, setting.MailService.FromEmail, subject, mailBody.String())

services/mailer/mail_repo.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
7979
}
8080

8181
for _, to := range emailTos {
82-
msg := NewMessage(to.EmailTo(), subject, content.String())
82+
msg := NewMessageFrom(to.EmailTo(), fromDisplayName(doer), setting.MailService.FromEmail, subject, content.String())
8383
msg.Info = fmt.Sprintf("UID: %d, repository pending transfer notification", newOwner.ID)
8484

8585
SendAsync(msg)

services/mailer/mail_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,51 @@ func TestGenerateMessageIDForRelease(t *testing.T) {
403403
})
404404
assert.Equal(t, "<owner/repo/releases/1@localhost>", msgID)
405405
}
406+
407+
func TestFromDisplayName(t *testing.T) {
408+
template, err := texttmpl.New("mailFrom").Parse("{{ .DisplayName }}")
409+
assert.NoError(t, err)
410+
setting.MailService = &setting.Mailer{FromDisplayNameFormatTemplate: template}
411+
defer func() { setting.MailService = nil }()
412+
413+
tests := []struct {
414+
userDisplayName string
415+
fromDisplayName string
416+
}{{
417+
userDisplayName: "test",
418+
fromDisplayName: "test",
419+
}, {
420+
userDisplayName: "Hi Its <Mee>",
421+
fromDisplayName: "Hi Its <Mee>",
422+
}, {
423+
userDisplayName: "Æsir",
424+
fromDisplayName: "=?utf-8?q?=C3=86sir?=",
425+
}, {
426+
userDisplayName: "new😀user",
427+
fromDisplayName: "=?utf-8?q?new=F0=9F=98=80user?=",
428+
}}
429+
430+
for _, tc := range tests {
431+
t.Run(tc.userDisplayName, func(t *testing.T) {
432+
user := &user_model.User{FullName: tc.userDisplayName, Name: "tmp"}
433+
got := fromDisplayName(user)
434+
assert.EqualValues(t, tc.fromDisplayName, got)
435+
})
436+
}
437+
438+
t.Run("template with all available vars", func(t *testing.T) {
439+
template, err = texttmpl.New("mailFrom").Parse("{{ .DisplayName }} (by {{ .AppName }} on [{{ .Domain }}])")
440+
assert.NoError(t, err)
441+
setting.MailService = &setting.Mailer{FromDisplayNameFormatTemplate: template}
442+
oldAppName := setting.AppName
443+
setting.AppName = "Code IT"
444+
oldDomain := setting.Domain
445+
setting.Domain = "code.it"
446+
defer func() {
447+
setting.AppName = oldAppName
448+
setting.Domain = oldDomain
449+
}()
450+
451+
assert.EqualValues(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"}))
452+
})
453+
}

0 commit comments

Comments
 (0)