Skip to content

Commit

Permalink
Add option for mailer to override mail headers (#27860)
Browse files Browse the repository at this point in the history
Add option to override headers of mails, gitea send out

---
*Sponsored by Kithara Software GmbH*
  • Loading branch information
6543 authored Jun 3, 2024
1 parent 8c68c5e commit aace3bc
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 10 deletions.
10 changes: 10 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1687,6 +1687,16 @@ LEVEL = Info
;; convert \r\n to \n for Sendmail
;SENDMAIL_CONVERT_CRLF = true

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[mailer.override_header]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This is empty by default, use it only if you know what you need it for.
;Reply-To = [email protected], [email protected]
;Content-Type = text/html; charset=utf-8
;In-Reply-To =

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;[email.incoming]
Expand Down
19 changes: 18 additions & 1 deletion docs/content/administration/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -724,11 +724,13 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type

## Mailer (`mailer`)

⚠️ This section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older,
:::warning
This section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older,
please refer to
[Gitea 1.17 app.ini example](https://github.com/go-gitea/gitea/blob/release/v1.17/custom/conf/app.example.ini)
and
[Gitea 1.17 configuration document](https://github.com/go-gitea/gitea/blob/release/v1.17/docs/content/doc/advanced/config-cheat-sheet.en-us.md)
:::

- `ENABLED`: **false**: Enable to use a mail service.
- `PROTOCOL`: **_empty_**: Mail server protocol. One of "smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy". _Before 1.18, this was inferred from a combination of `MAILER_TYPE` and `IS_TLS_ENABLED`._
Expand Down Expand Up @@ -761,6 +763,21 @@ and
- `SEND_BUFFER_LEN`: **100**: Buffer length of mailing queue. **DEPRECATED** use `LENGTH` in `[queue.mailer]`
- `SEND_AS_PLAIN_TEXT`: **false**: Send mails only in plain text, without HTML alternative.

## Override Email Headers (`mailer.override_header`)

:::warning
This is empty by default, use it only if you know what you need it for.
:::

examples would be:

```ini
[mailer.override_header]
Reply-To = [email protected], [email protected]
Content-Type = text/html; charset=utf-8
In-Reply-To =
```

## Incoming Email (`email.incoming`)

- `ENABLED`: **false**: Enable handling of incoming emails.
Expand Down
23 changes: 15 additions & 8 deletions modules/setting/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ import (
// Mailer represents mail service.
type Mailer struct {
// Mailer
Name string `ini:"NAME"`
From string `ini:"FROM"`
EnvelopeFrom string `ini:"ENVELOPE_FROM"`
OverrideEnvelopeFrom bool `ini:"-"`
FromName string `ini:"-"`
FromEmail string `ini:"-"`
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
SubjectPrefix string `ini:"SUBJECT_PREFIX"`
Name string `ini:"NAME"`
From string `ini:"FROM"`
EnvelopeFrom string `ini:"ENVELOPE_FROM"`
OverrideEnvelopeFrom bool `ini:"-"`
FromName string `ini:"-"`
FromEmail string `ini:"-"`
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
SubjectPrefix string `ini:"SUBJECT_PREFIX"`
OverrideHeader map[string][]string `ini:"-"`

// SMTP sender
Protocol string `ini:"PROTOCOL"`
Expand Down Expand Up @@ -151,6 +152,12 @@ func loadMailerFrom(rootCfg ConfigProvider) {
log.Fatal("Unable to map [mailer] section on to MailService. Error: %v", err)
}

overrideHeader := rootCfg.Section("mailer.override_header").Keys()
MailService.OverrideHeader = make(map[string][]string)
for _, key := range overrideHeader {
MailService.OverrideHeader[key.Name()] = key.Strings(",")
}

// Infer SMTPPort if not set
if MailService.SMTPPort == "" {
switch MailService.Protocol {
Expand Down
10 changes: 9 additions & 1 deletion services/mailer/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (m *Message) ToMessage() *gomail.Message {
msg.SetHeader(header, m.Headers[header]...)
}

if len(setting.MailService.SubjectPrefix) > 0 {
if setting.MailService.SubjectPrefix != "" {
msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
} else {
msg.SetHeader("Subject", m.Subject)
Expand All @@ -79,6 +79,14 @@ func (m *Message) ToMessage() *gomail.Message {
if len(msg.GetHeader("Message-ID")) == 0 {
msg.SetHeader("Message-ID", m.generateAutoMessageID())
}

for k, v := range setting.MailService.OverrideHeader {
if len(msg.GetHeader(k)) != 0 {
log.Debug("Mailer override header '%s' as per config", k)
}
msg.SetHeader(k, v...)
}

return msg
}

Expand Down
76 changes: 76 additions & 0 deletions services/mailer/mailer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package mailer

import (
"strings"
"testing"
"time"

Expand Down Expand Up @@ -36,3 +37,78 @@ func TestGenerateMessageID(t *testing.T) {
gm = m.ToMessage()
assert.Equal(t, "<[email protected]>", gm.GetHeader("Message-ID")[0])
}

func TestToMessage(t *testing.T) {
oldConf := *setting.MailService
defer func() {
setting.MailService = &oldConf
}()
setting.MailService.From = "[email protected]"

m1 := Message{
Info: "info",
FromAddress: "[email protected]",
FromDisplayName: "Test Gitea",
To: "[email protected]",
Subject: "Issue X Closed",
Body: "Some Issue got closed by Y-Man",
}

buf := &strings.Builder{}
_, err := m1.ToMessage().WriteTo(buf)
assert.NoError(t, err)
header, _ := extractMailHeaderAndContent(t, buf.String())
assert.EqualValues(t, map[string]string{
"Content-Type": "multipart/alternative;",
"Date": "Mon, 01 Jan 0001 00:00:00 +0000",
"From": "\"Test Gitea\" <[email protected]>",
"Message-ID": "<autogen--6795364578871-69c000786adc60dc@localhost>",
"Mime-Version": "1.0",
"Subject": "Issue X Closed",
"To": "[email protected]",
"X-Auto-Response-Suppress": "All",
}, header)

setting.MailService.OverrideHeader = map[string][]string{
"Message-ID": {""}, // delete message id
"Auto-Submitted": {"auto-generated"}, // suppress auto replay
}

buf = &strings.Builder{}
_, err = m1.ToMessage().WriteTo(buf)
assert.NoError(t, err)
header, _ = extractMailHeaderAndContent(t, buf.String())
assert.EqualValues(t, map[string]string{
"Content-Type": "multipart/alternative;",
"Date": "Mon, 01 Jan 0001 00:00:00 +0000",
"From": "\"Test Gitea\" <[email protected]>",
"Message-ID": "",
"Mime-Version": "1.0",
"Subject": "Issue X Closed",
"To": "[email protected]",
"X-Auto-Response-Suppress": "All",
"Auto-Submitted": "auto-generated",
}, header)
}

func extractMailHeaderAndContent(t *testing.T, mail string) (map[string]string, string) {
header := make(map[string]string)

parts := strings.SplitN(mail, "boundary=", 2)
if !assert.Len(t, parts, 2) {
return nil, ""
}
content := strings.TrimSpace("boundary=" + parts[1])

hParts := strings.Split(parts[0], "\n")

for _, hPart := range hParts {
parts := strings.SplitN(hPart, ":", 2)
hk := strings.TrimSpace(parts[0])
if hk != "" {
header[hk] = strings.TrimSpace(parts[1])
}
}

return header, content
}

0 comments on commit aace3bc

Please sign in to comment.