Skip to content

Commit aace3bc

Browse files
authored
Add option for mailer to override mail headers (#27860)
Add option to override headers of mails, gitea send out --- *Sponsored by Kithara Software GmbH*
1 parent 8c68c5e commit aace3bc

File tree

5 files changed

+128
-10
lines changed

5 files changed

+128
-10
lines changed

custom/conf/app.example.ini

+10
Original file line numberDiff line numberDiff line change
@@ -1687,6 +1687,16 @@ LEVEL = Info
16871687
;; convert \r\n to \n for Sendmail
16881688
;SENDMAIL_CONVERT_CRLF = true
16891689

1690+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1691+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1692+
;[mailer.override_header]
1693+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1694+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1695+
;; This is empty by default, use it only if you know what you need it for.
1696+
1697+
;Content-Type = text/html; charset=utf-8
1698+
;In-Reply-To =
1699+
16901700
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
16911701
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
16921702
;[email.incoming]

docs/content/administration/config-cheat-sheet.en-us.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -724,11 +724,13 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type
724724

725725
## Mailer (`mailer`)
726726

727-
⚠️ This section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older,
727+
:::warning
728+
This section is for Gitea 1.18 and later. If you are using Gitea 1.17 or older,
728729
please refer to
729730
[Gitea 1.17 app.ini example](https://github.com/go-gitea/gitea/blob/release/v1.17/custom/conf/app.example.ini)
730731
and
731732
[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)
733+
:::
732734

733735
- `ENABLED`: **false**: Enable to use a mail service.
734736
- `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`._
@@ -761,6 +763,21 @@ and
761763
- `SEND_BUFFER_LEN`: **100**: Buffer length of mailing queue. **DEPRECATED** use `LENGTH` in `[queue.mailer]`
762764
- `SEND_AS_PLAIN_TEXT`: **false**: Send mails only in plain text, without HTML alternative.
763765

766+
## Override Email Headers (`mailer.override_header`)
767+
768+
:::warning
769+
This is empty by default, use it only if you know what you need it for.
770+
:::
771+
772+
examples would be:
773+
774+
```ini
775+
[mailer.override_header]
776+
777+
Content-Type = text/html; charset=utf-8
778+
In-Reply-To =
779+
```
780+
764781
## Incoming Email (`email.incoming`)
765782

766783
- `ENABLED`: **false**: Enable handling of incoming emails.

modules/setting/mailer.go

+15-8
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ import (
1818
// Mailer represents mail service.
1919
type Mailer struct {
2020
// Mailer
21-
Name string `ini:"NAME"`
22-
From string `ini:"FROM"`
23-
EnvelopeFrom string `ini:"ENVELOPE_FROM"`
24-
OverrideEnvelopeFrom bool `ini:"-"`
25-
FromName string `ini:"-"`
26-
FromEmail string `ini:"-"`
27-
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
28-
SubjectPrefix string `ini:"SUBJECT_PREFIX"`
21+
Name string `ini:"NAME"`
22+
From string `ini:"FROM"`
23+
EnvelopeFrom string `ini:"ENVELOPE_FROM"`
24+
OverrideEnvelopeFrom bool `ini:"-"`
25+
FromName string `ini:"-"`
26+
FromEmail string `ini:"-"`
27+
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
28+
SubjectPrefix string `ini:"SUBJECT_PREFIX"`
29+
OverrideHeader map[string][]string `ini:"-"`
2930

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

155+
overrideHeader := rootCfg.Section("mailer.override_header").Keys()
156+
MailService.OverrideHeader = make(map[string][]string)
157+
for _, key := range overrideHeader {
158+
MailService.OverrideHeader[key.Name()] = key.Strings(",")
159+
}
160+
154161
// Infer SMTPPort if not set
155162
if MailService.SMTPPort == "" {
156163
switch MailService.Protocol {

services/mailer/mailer.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (m *Message) ToMessage() *gomail.Message {
5757
msg.SetHeader(header, m.Headers[header]...)
5858
}
5959

60-
if len(setting.MailService.SubjectPrefix) > 0 {
60+
if setting.MailService.SubjectPrefix != "" {
6161
msg.SetHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
6262
} else {
6363
msg.SetHeader("Subject", m.Subject)
@@ -79,6 +79,14 @@ func (m *Message) ToMessage() *gomail.Message {
7979
if len(msg.GetHeader("Message-ID")) == 0 {
8080
msg.SetHeader("Message-ID", m.generateAutoMessageID())
8181
}
82+
83+
for k, v := range setting.MailService.OverrideHeader {
84+
if len(msg.GetHeader(k)) != 0 {
85+
log.Debug("Mailer override header '%s' as per config", k)
86+
}
87+
msg.SetHeader(k, v...)
88+
}
89+
8290
return msg
8391
}
8492

services/mailer/mailer_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package mailer
55

66
import (
7+
"strings"
78
"testing"
89
"time"
910

@@ -36,3 +37,78 @@ func TestGenerateMessageID(t *testing.T) {
3637
gm = m.ToMessage()
3738
assert.Equal(t, "<[email protected]>", gm.GetHeader("Message-ID")[0])
3839
}
40+
41+
func TestToMessage(t *testing.T) {
42+
oldConf := *setting.MailService
43+
defer func() {
44+
setting.MailService = &oldConf
45+
}()
46+
setting.MailService.From = "[email protected]"
47+
48+
m1 := Message{
49+
Info: "info",
50+
FromAddress: "[email protected]",
51+
FromDisplayName: "Test Gitea",
52+
53+
Subject: "Issue X Closed",
54+
Body: "Some Issue got closed by Y-Man",
55+
}
56+
57+
buf := &strings.Builder{}
58+
_, err := m1.ToMessage().WriteTo(buf)
59+
assert.NoError(t, err)
60+
header, _ := extractMailHeaderAndContent(t, buf.String())
61+
assert.EqualValues(t, map[string]string{
62+
"Content-Type": "multipart/alternative;",
63+
"Date": "Mon, 01 Jan 0001 00:00:00 +0000",
64+
"From": "\"Test Gitea\" <[email protected]>",
65+
"Message-ID": "<autogen--6795364578871-69c000786adc60dc@localhost>",
66+
"Mime-Version": "1.0",
67+
"Subject": "Issue X Closed",
68+
69+
"X-Auto-Response-Suppress": "All",
70+
}, header)
71+
72+
setting.MailService.OverrideHeader = map[string][]string{
73+
"Message-ID": {""}, // delete message id
74+
"Auto-Submitted": {"auto-generated"}, // suppress auto replay
75+
}
76+
77+
buf = &strings.Builder{}
78+
_, err = m1.ToMessage().WriteTo(buf)
79+
assert.NoError(t, err)
80+
header, _ = extractMailHeaderAndContent(t, buf.String())
81+
assert.EqualValues(t, map[string]string{
82+
"Content-Type": "multipart/alternative;",
83+
"Date": "Mon, 01 Jan 0001 00:00:00 +0000",
84+
"From": "\"Test Gitea\" <[email protected]>",
85+
"Message-ID": "",
86+
"Mime-Version": "1.0",
87+
"Subject": "Issue X Closed",
88+
89+
"X-Auto-Response-Suppress": "All",
90+
"Auto-Submitted": "auto-generated",
91+
}, header)
92+
}
93+
94+
func extractMailHeaderAndContent(t *testing.T, mail string) (map[string]string, string) {
95+
header := make(map[string]string)
96+
97+
parts := strings.SplitN(mail, "boundary=", 2)
98+
if !assert.Len(t, parts, 2) {
99+
return nil, ""
100+
}
101+
content := strings.TrimSpace("boundary=" + parts[1])
102+
103+
hParts := strings.Split(parts[0], "\n")
104+
105+
for _, hPart := range hParts {
106+
parts := strings.SplitN(hPart, ":", 2)
107+
hk := strings.TrimSpace(parts[0])
108+
if hk != "" {
109+
header[hk] = strings.TrimSpace(parts[1])
110+
}
111+
}
112+
113+
return header, content
114+
}

0 commit comments

Comments
 (0)