mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-03 21:08:25 +00:00 
			
		
		
		
	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:
		@@ -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 =
 | 
				
			||||||
;;
 | 
					;;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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"}))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user