Skip to content

Commit 36c7b62

Browse files
authored
Merge pull request #472 from wneessen/feature/467_setting-addr-headers-from-mailaddress-instances
Add support for mail.Address instances
2 parents 6d381f4 + 5d2a1bc commit 36c7b62

File tree

5 files changed

+674
-1
lines changed

5 files changed

+674
-1
lines changed

header.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package mail
66

7+
import "strings"
8+
79
// Header is a type wrapper for a string and represents email header fields in a Msg.
810
type Header string
911

@@ -115,7 +117,7 @@ const (
115117
// HeaderReplyTo is the "Reply-To" header field.
116118
HeaderReplyTo AddrHeader = "Reply-To"
117119

118-
// HeaderTo is the "Receipient" header field.
120+
// HeaderTo is the "Recipient" header field.
119121
HeaderTo AddrHeader = "To"
120122
)
121123

@@ -222,3 +224,18 @@ func (h Header) String() string {
222224
func (a AddrHeader) String() string {
223225
return string(a)
224226
}
227+
228+
// IsAddrHeader checks if the provided string is an address header. It returns true on a valid AddrHeader
229+
// and false for any other string.
230+
//
231+
// Parameters:
232+
// - header: The string to check.
233+
func IsAddrHeader(header string) bool {
234+
addrHeaderList := []string{"bcc", "cc", "envelopefrom", "from", "reply-to", "to"}
235+
for _, addrHeader := range addrHeaderList {
236+
if addrHeader == strings.ToLower(header) {
237+
return true
238+
}
239+
}
240+
return false
241+
}

header_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ var (
4848
header AddrHeader
4949
want string
5050
}{
51+
{"EnvelopeFrom", HeaderEnvelopeFrom, "EnvelopeFrom"},
5152
{"From", HeaderFrom, "From"},
5253
{"To", HeaderTo, "To"},
5354
{"Cc", HeaderCc, "Cc"},
@@ -123,3 +124,46 @@ func TestHeader_Stringer(t *testing.T) {
123124
})
124125
}
125126
}
127+
128+
func TestIsAddrHeader(t *testing.T) {
129+
t.Run("validate all address headers", func(t *testing.T) {
130+
for _, tt := range addrHeaderTests {
131+
t.Run(tt.name+" is a valid address header", func(t *testing.T) {
132+
if !IsAddrHeader(tt.header.String()) {
133+
t.Errorf("expected %s to be a valid address header", tt.header.String())
134+
}
135+
})
136+
}
137+
})
138+
t.Run("validate all non-address headers", func(t *testing.T) {
139+
for _, tt := range genHeaderTests {
140+
t.Run(tt.name+" is not an address header", func(t *testing.T) {
141+
if IsAddrHeader(tt.header.String()) {
142+
t.Errorf("expected %s to not be an address header", tt.header.String())
143+
}
144+
})
145+
}
146+
})
147+
t.Run("validate mixed headers from map", func(t *testing.T) {
148+
someHeaders := map[string]string{
149+
"To": "toni.tester@example.com",
150+
"From": "tina.tester@example.com",
151+
"Subject": "this is the subject",
152+
"Message-ID": "test.mail.1234567@localhost.local",
153+
}
154+
message := NewMsg()
155+
for k, v := range someHeaders {
156+
if IsAddrHeader(k) {
157+
if err := message.SetAddrHeader(AddrHeader(k), v); err != nil {
158+
t.Errorf("failed to set address header: %s", err)
159+
}
160+
continue
161+
}
162+
message.SetGenHeader(Header(k), v)
163+
}
164+
checkAddrHeader(t, message, HeaderTo, "IsAddrHeader", 0, 1, "toni.tester@example.com", "")
165+
checkAddrHeader(t, message, HeaderFrom, "IsAddrHeader", 0, 1, "tina.tester@example.com", "")
166+
checkGenHeader(t, message, HeaderSubject, "IsAddrHeader", 0, 1, "this is the subject")
167+
checkGenHeader(t, message, HeaderMessageID, "IsAddrHeader", 0, 1, "test.mail.1234567@localhost.local")
168+
})
169+
}

msg.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,9 @@ func (m *Msg) SetGenHeaderPreformatted(header Header, value string) {
563563
// - values: One or more string values representing the email addresses to associate with
564564
// the specified header.
565565
//
566+
// Returns:
567+
// - An error if parsing the address according to RFC 5322 fails
568+
//
566569
// References:
567570
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.4
568571
func (m *Msg) SetAddrHeader(header AddrHeader, values ...string) error {
@@ -588,6 +591,45 @@ func (m *Msg) SetAddrHeader(header AddrHeader, values ...string) error {
588591
return nil
589592
}
590593

594+
// SetAddrHeaderFromMailAddress sets the specified AddrHeader for the Msg to the given mail.Address values.
595+
//
596+
// This method allows you to set address-related headers for the message, with mail.Address instances
597+
// as input. Using this method helps maintain the integrity of the email addresses within the message.
598+
//
599+
// Since we expect the mail.Address instances to be already parsed according to RFC 5322, this method
600+
// will not attempt to perform any sanity checks except of nil pointers and therefore no error will
601+
// be returned. Nil pointers will be silently ignored.
602+
//
603+
// Parameters:
604+
// - header: The AddrHeader to set in the Msg (e.g., "From", "To", "Cc", "Bcc").
605+
// - addresses: One or more mail.Address pointers representing the email addresses to associate with
606+
// the specified header.
607+
//
608+
// References:
609+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.4
610+
func (m *Msg) SetAddrHeaderFromMailAddress(header AddrHeader, values ...*mail.Address) {
611+
if m.addrHeader == nil {
612+
m.addrHeader = make(map[AddrHeader][]*mail.Address)
613+
}
614+
615+
var addresses []*mail.Address
616+
for _, addrVal := range values {
617+
if addrVal == nil {
618+
continue
619+
}
620+
addresses = append(addresses, addrVal)
621+
}
622+
623+
switch header {
624+
case HeaderEnvelopeFrom, HeaderFrom, HeaderReplyTo:
625+
if len(addresses) > 0 {
626+
m.addrHeader[header] = []*mail.Address{addresses[0]}
627+
}
628+
default:
629+
m.addrHeader[header] = addresses
630+
}
631+
}
632+
591633
// SetAddrHeaderIgnoreInvalid sets the specified AddrHeader for the Msg to the given values.
592634
//
593635
// Addresses are parsed according to RFC 5322. If parsing of any of the provided values fails,
@@ -659,6 +701,23 @@ func (m *Msg) EnvelopeFromFormat(name, addr string) error {
659701
return m.SetAddrHeader(HeaderEnvelopeFrom, fmt.Sprintf(`"%s" <%s>`, name, addr))
660702
}
661703

704+
// EnvelopeFromMailAddress sets the "FROM" address in the mail body for the Msg using a mail.Address instance.
705+
//
706+
// The HeaderEnvelopeFrom address is generally not included in the mail body but only used by the
707+
// Client for communication with the SMTP server. If the Msg has no "FROM" address set in the mail
708+
// body, the msgWriter will try to use the envelope from address if it has been set for the Msg.
709+
// The provided name and address are validated according to RFC 5322 and will return an error if
710+
// the validation fails.
711+
//
712+
// Parameters:
713+
// - addr: The address as mail.Address instance to be set as envelope from address.
714+
//
715+
// References:
716+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2
717+
func (m *Msg) EnvelopeFromMailAddress(addr *mail.Address) {
718+
m.SetAddrHeaderFromMailAddress(HeaderEnvelopeFrom, addr)
719+
}
720+
662721
// From sets the "FROM" address in the mail body for the Msg.
663722
//
664723
// The "FROM" address is included in the mail body and indicates the sender of the message to
@@ -676,6 +735,22 @@ func (m *Msg) From(from string) error {
676735
return m.SetAddrHeader(HeaderFrom, from)
677736
}
678737

738+
// FromMailAddress sets the "FROM" address in the mail body for the Msg using a mail.Address instance.
739+
//
740+
// The "FROM" address is included in the mail body and indicates the sender of the message to
741+
// the recipient. This address is visible in the email client and is typically displayed to the
742+
// recipient. If the "FROM" address is not set, the msgWriter may attempt to use the envelope
743+
// from address (if available) for sending.
744+
//
745+
// Parameters:
746+
// - from: The "FROM" address to set in the mail body as *mail.Address.
747+
//
748+
// References:
749+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.2
750+
func (m *Msg) FromMailAddress(from *mail.Address) {
751+
m.SetAddrHeaderFromMailAddress(HeaderFrom, from)
752+
}
753+
679754
// FromFormat sets the provided name and mail address as the "FROM" address in the mail body for the Msg.
680755
//
681756
// The "FROM" address is included in the mail body and indicates the sender of the message to
@@ -710,6 +785,22 @@ func (m *Msg) To(rcpts ...string) error {
710785
return m.SetAddrHeader(HeaderTo, rcpts...)
711786
}
712787

788+
// ToMailAddress sets one or more "TO" addresses in the mail body for the Msg.
789+
//
790+
// The "TO" address specifies the primary recipient(s) of the message and is included in the mail body.
791+
// This address is visible to the recipient and any other recipients of the message. Multiple "TO" addresses
792+
// can be set by passing them as variadic arguments to this method.
793+
//
794+
// Parameters:
795+
// - rcpts: One or more recipient email addresses as mail.Address instance to include
796+
// in the "TO" field.
797+
//
798+
// References:
799+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
800+
func (m *Msg) ToMailAddress(rcpts ...*mail.Address) {
801+
m.SetAddrHeaderFromMailAddress(HeaderTo, rcpts...)
802+
}
803+
713804
// AddTo adds a single "TO" address to the existing list of recipients in the mail body for the Msg.
714805
//
715806
// This method allows you to add a single recipient to the "TO" field without replacing any previously set
@@ -726,6 +817,23 @@ func (m *Msg) AddTo(rcpt string) error {
726817
return m.addAddr(HeaderTo, rcpt)
727818
}
728819

820+
// AddToMailAddress adds a single "TO" address to the existing list of recipients in the mail body for the Msg.
821+
//
822+
// This method allows you to add a single recipient to the "TO" field without replacing any previously set
823+
// "TO" addresses. The "TO" address specifies the primary recipient(s) of the message and is visible in the mail
824+
// client. Since the provided mail.Address has already been validated, no further validation is performed in
825+
// this method and the values are used as given.
826+
//
827+
// Parameters:
828+
// - rcpt: The recipient email address as *mail.Address to add to the "TO" field.
829+
//
830+
// References:
831+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
832+
func (m *Msg) AddToMailAddress(rcpt *mail.Address) {
833+
addresses := append(m.addrHeader[HeaderTo], rcpt)
834+
m.SetAddrHeaderFromMailAddress(HeaderTo, addresses...)
835+
}
836+
729837
// AddToFormat adds a single "TO" address with the provided name and email to the existing list of recipients
730838
// in the mail body for the Msg.
731839
//
@@ -802,6 +910,22 @@ func (m *Msg) Cc(rcpts ...string) error {
802910
return m.SetAddrHeader(HeaderCc, rcpts...)
803911
}
804912

913+
// CcMailAddress sets one or more "CC" (carbon copy) addresses in the mail body for the Msg.
914+
//
915+
// The "CC" address specifies secondary recipient(s) of the message, and is included in the mail body.
916+
// This address is visible to the recipient and any other recipients of the message. Multiple "CC" addresses
917+
// can be set by passing them as variadic arguments to this method.
918+
//
919+
// Parameters:
920+
// - rcpts: One or more recipient email addresses as mail.Address instance to include
921+
// in the "CC" field.
922+
//
923+
// References:
924+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
925+
func (m *Msg) CcMailAddress(rcpts ...*mail.Address) {
926+
m.SetAddrHeaderFromMailAddress(HeaderCc, rcpts...)
927+
}
928+
805929
// AddCc adds a single "CC" (carbon copy) address to the existing list of "CC" recipients in the mail body
806930
// for the Msg.
807931
//
@@ -819,6 +943,23 @@ func (m *Msg) AddCc(rcpt string) error {
819943
return m.addAddr(HeaderCc, rcpt)
820944
}
821945

946+
// AddCcMailAddress adds a single "CC" address to the existing list of recipients in the mail body for the Msg.
947+
//
948+
// This method allows you to add a single recipient to the "CC" field without replacing any previously set "CC"
949+
// addresses. The "CC" address specifies secondary recipient(s) and is visible to all recipients, including those
950+
// in the "CC" field. Since the provided mail.Address has already been validated, no further validation is
951+
// performed in this method and the values are used as given.
952+
//
953+
// Parameters:
954+
// - rcpt: The recipient email address as *mail.Address to add to the "CC" field.
955+
//
956+
// References:
957+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
958+
func (m *Msg) AddCcMailAddress(rcpt *mail.Address) {
959+
addresses := append(m.addrHeader[HeaderCc], rcpt)
960+
m.SetAddrHeaderFromMailAddress(HeaderCc, addresses...)
961+
}
962+
822963
// AddCcFormat adds a single "CC" (carbon copy) address with the provided name and email to the existing list
823964
// of "CC" recipients in the mail body for the Msg.
824965
//
@@ -897,6 +1038,22 @@ func (m *Msg) Bcc(rcpts ...string) error {
8971038
return m.SetAddrHeader(HeaderBcc, rcpts...)
8981039
}
8991040

1041+
// BccMailAddress sets one or more "BCC" (blind carbon copy) addresses in the mail body for the Msg.
1042+
//
1043+
// The "BCC" address specifies recipient(s) of the message who will receive a copy without other recipients
1044+
// being aware of it. These addresses are not visible in the mail body or to any other recipients, ensuring
1045+
// the privacy of BCC'd recipients. Multiple "BCC" addresses can be set by passing them as variadic arguments
1046+
// arguments to this method.
1047+
//
1048+
// Parameters:
1049+
// - rcpts: One or more recipient email addresses as mail.Address instance to include in the "BCC" field.
1050+
//
1051+
// References:
1052+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
1053+
func (m *Msg) BccMailAddress(rcpts ...*mail.Address) {
1054+
m.SetAddrHeaderFromMailAddress(HeaderBcc, rcpts...)
1055+
}
1056+
9001057
// AddBcc adds a single "BCC" (blind carbon copy) address to the existing list of "BCC" recipients in the mail
9011058
// body for the Msg.
9021059
//
@@ -914,6 +1071,22 @@ func (m *Msg) AddBcc(rcpt string) error {
9141071
return m.addAddr(HeaderBcc, rcpt)
9151072
}
9161073

1074+
// AddBccMailAddress adds a single "BCC" address to the existing list of recipients in the mail body for the Msg.
1075+
//
1076+
// This method allows you to add a single recipient to the "BCC" field without replacing any previously set
1077+
// "BCC" addresses. The "BCC" address specifies recipient(s) of the message who will receive a copy without other
1078+
// recipients being aware of it.
1079+
//
1080+
// Parameters:
1081+
// - rcpt: The recipient email address as *mail.Address to add to the "BCC" field.
1082+
//
1083+
// References:
1084+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
1085+
func (m *Msg) AddBccMailAddress(rcpt *mail.Address) {
1086+
addresses := append(m.addrHeader[HeaderBcc], rcpt)
1087+
m.SetAddrHeaderFromMailAddress(HeaderBcc, addresses...)
1088+
}
1089+
9171090
// AddBccFormat adds a single "BCC" (blind carbon copy) address with the provided name and email to the existing
9181091
// list of "BCC" recipients in the mail body for the Msg.
9191092
//
@@ -991,6 +1164,20 @@ func (m *Msg) ReplyTo(addr string) error {
9911164
return m.SetAddrHeader(HeaderReplyTo, addr)
9921165
}
9931166

1167+
// ReplyToMailAddress sets one or more "BCC" (blind carbon copy) addresses in the mail body for the Msg.
1168+
//
1169+
// The "Reply-To" address can be different from the "From" address, allowing the sender to specify an alternate
1170+
// address for responses.
1171+
//
1172+
// Parameters:
1173+
// - addr: The mail.Address instance to set as the "Reply-To" address.
1174+
//
1175+
// References:
1176+
// - https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
1177+
func (m *Msg) ReplyToMailAddress(addr *mail.Address) {
1178+
m.SetAddrHeaderFromMailAddress(HeaderReplyTo, addr)
1179+
}
1180+
9941181
// ReplyToFormat sets the "Reply-To" address for the Msg using the provided name and email address, specifying
9951182
// where replies should be sent.
9961183
//

0 commit comments

Comments
 (0)