aboutsummaryrefslogtreecommitdiffstats
path: root/services/mailer/sender/message.go
blob: db20675572d204f6e99611501c87b8d8eb4077a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package sender

import (
	"fmt"
	"hash/fnv"
	"net/mail"
	"strings"
	"time"

	"code.gitea.io/gitea/modules/base"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"

	"github.com/jaytaylor/html2text"
	gomail "github.com/wneessen/go-mail"
)

// Message mail body and log info
type Message struct {
	Info            string // Message information for log purpose.
	FromAddress     string
	FromDisplayName string
	To              string // Use only one recipient to prevent leaking of addresses
	ReplyTo         string
	Subject         string
	Date            time.Time
	Body            string
	Headers         map[string][]string
}

// ToMessage converts a Message to gomail.Message
func (m *Message) ToMessage() *gomail.Msg {
	msg := gomail.NewMsg()
	addr := mail.Address{Name: m.FromDisplayName, Address: m.FromAddress}
	_ = msg.SetAddrHeader("From", addr.String())
	_ = msg.SetAddrHeader("To", m.To)
	if m.ReplyTo != "" {
		msg.SetGenHeader("Reply-To", m.ReplyTo)
	}
	for header := range m.Headers {
		msg.SetGenHeader(gomail.Header(header), m.Headers[header]...)
	}

	if setting.MailService.SubjectPrefix != "" {
		msg.SetGenHeader("Subject", setting.MailService.SubjectPrefix+" "+m.Subject)
	} else {
		msg.SetGenHeader("Subject", m.Subject)
	}
	msg.SetDateWithValue(m.Date)
	msg.SetGenHeader("X-Auto-Response-Suppress", "All")

	plainBody, err := html2text.FromString(m.Body)
	if err != nil || setting.MailService.SendAsPlainText {
		if strings.Contains(base.TruncateString(m.Body, 100), "<html>") {
			log.Warn("Mail contains HTML but configured to send as plain text.")
		}
		msg.SetBodyString("text/plain", plainBody)
	} else {
		msg.SetBodyString("text/plain", plainBody)
		msg.AddAlternativeString("text/html", m.Body)
	}

	if len(msg.GetGenHeader("Message-ID")) == 0 {
		msg.SetGenHeader("Message-ID", m.generateAutoMessageID())
	}

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

	return msg
}

// SetHeader adds additional headers to a message
func (m *Message) SetHeader(field string, value ...string) {
	m.Headers[field] = value
}

func (m *Message) generateAutoMessageID() string {
	dateMs := m.Date.UnixNano() / 1e6
	h := fnv.New64()
	if len(m.To) > 0 {
		_, _ = h.Write([]byte(m.To))
	}
	_, _ = h.Write([]byte(m.Subject))
	_, _ = h.Write([]byte(m.Body))
	return fmt.Sprintf("<autogen-%d-%016x@%s>", dateMs, h.Sum64(), setting.Domain)
}

// NewMessageFrom creates new mail message object with custom From header.
func NewMessageFrom(to, fromDisplayName, fromAddress, subject, body string) *Message {
	log.Trace("NewMessageFrom (body):\n%s", body)

	return &Message{
		FromAddress:     fromAddress,
		FromDisplayName: fromDisplayName,
		To:              to,
		Subject:         subject,
		Date:            time.Now(),
		Body:            body,
		Headers:         map[string][]string{},
	}
}

// NewMessage creates new mail message object with default From header.
func NewMessage(to, subject, body string) *Message {
	return NewMessageFrom(to, setting.MailService.FromName, setting.MailService.FromEmail, subject, body)
}