blob: 1d21f588635c972f0adae4763a6bf8bc51846a38 [file] [log] [blame]
Dusan Kasan17e497e2017-04-10 22:44:22 +02001package parsemail
2
3import (
Dusan Kasan17e497e2017-04-10 22:44:22 +02004 "bytes"
Dusan Kasan4595dfe2017-04-13 00:38:24 +02005 "encoding/base64"
Dusan Kasan4595dfe2017-04-13 00:38:24 +02006 "fmt"
7 "io"
8 "io/ioutil"
9 "mime"
10 "mime/multipart"
11 "net/mail"
12 "strings"
13 "time"
Dusan Kasan17e497e2017-04-10 22:44:22 +020014)
15
Dusan Kasan45ca2642017-04-18 10:39:35 +020016const contentTypeMultipartMixed = "multipart/mixed"
17const contentTypeMultipartAlternative = "multipart/alternative"
18const contentTypeMultipartRelated = "multipart/related"
19const contentTypeTextHtml = "text/html"
20const contentTypeTextPlain = "text/plain"
Dusan Kasan17e497e2017-04-10 22:44:22 +020021
Dusan Kasan45ca2642017-04-18 10:39:35 +020022// Parse an email message read from io.Reader into parsemail.Email struct
Dusan Kasanb49ceb62017-04-13 00:00:36 +020023func Parse(r io.Reader) (email Email, err error) {
Dusan Kasan4595dfe2017-04-13 00:38:24 +020024 msg, err := mail.ReadMessage(r)
Dusan Kasan17e497e2017-04-10 22:44:22 +020025 if err != nil {
Dusan Kasanb49ceb62017-04-13 00:00:36 +020026 return
Dusan Kasan17e497e2017-04-10 22:44:22 +020027 }
28
Dusan Kasanb49ceb62017-04-13 00:00:36 +020029 email, err = createEmailFromHeader(msg.Header)
Dusan Kasan17e497e2017-04-10 22:44:22 +020030 if err != nil {
Dusan Kasanb49ceb62017-04-13 00:00:36 +020031 return
Dusan Kasan17e497e2017-04-10 22:44:22 +020032 }
33
Dusan Kasan428369f2020-02-24 00:47:31 +010034 email.ContentType = msg.Header.Get("Content-Type")
35 contentType, params, err := parseContentType(email.ContentType)
Dusan Kasan17e497e2017-04-10 22:44:22 +020036 if err != nil {
Dusan Kasanb49ceb62017-04-13 00:00:36 +020037 return
Dusan Kasan17e497e2017-04-10 22:44:22 +020038 }
39
Dusan Kasanb49ceb62017-04-13 00:00:36 +020040 switch contentType {
Dusan Kasan45ca2642017-04-18 10:39:35 +020041 case contentTypeMultipartMixed:
Dusan Kasan17e497e2017-04-10 22:44:22 +020042 email.TextBody, email.HTMLBody, email.Attachments, email.EmbeddedFiles, err = parseMultipartMixed(msg.Body, params["boundary"])
Dusan Kasan45ca2642017-04-18 10:39:35 +020043 case contentTypeMultipartAlternative:
Dusan Kasan17e497e2017-04-10 22:44:22 +020044 email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartAlternative(msg.Body, params["boundary"])
Obi Symons89230f42020-04-04 14:32:11 +110045 case contentTypeMultipartRelated:
46 email.TextBody, email.HTMLBody, email.EmbeddedFiles, err = parseMultipartRelated(msg.Body, params["boundary"])
Dusan Kasan45ca2642017-04-18 10:39:35 +020047 case contentTypeTextPlain:
Dusan Kasan17e497e2017-04-10 22:44:22 +020048 message, _ := ioutil.ReadAll(msg.Body)
49 email.TextBody = strings.TrimSuffix(string(message[:]), "\n")
Dusan Kasan45ca2642017-04-18 10:39:35 +020050 case contentTypeTextHtml:
Dusan Kasan17e497e2017-04-10 22:44:22 +020051 message, _ := ioutil.ReadAll(msg.Body)
52 email.HTMLBody = strings.TrimSuffix(string(message[:]), "\n")
Dusan Kasanb49ceb62017-04-13 00:00:36 +020053 default:
Dusan Kasan428369f2020-02-24 00:47:31 +010054 email.Content, err = decodeContent(msg.Body, msg.Header.Get("Content-Transfer-Encoding"))
Dusan Kasan17e497e2017-04-10 22:44:22 +020055 }
56
Dusan Kasanb49ceb62017-04-13 00:00:36 +020057 return
58}
59
60func createEmailFromHeader(header mail.Header) (email Email, err error) {
Dusan Kasane668cf22017-04-18 12:56:51 +020061 hp := headerParser{header: &header}
62
Dusan Kasanf4376a62017-05-23 21:03:55 +020063 email.Subject = decodeMimeSentence(header.Get("Subject"))
Dusan Kasane668cf22017-04-18 12:56:51 +020064 email.From = hp.parseAddressList(header.Get("From"))
65 email.Sender = hp.parseAddress(header.Get("Sender"))
66 email.ReplyTo = hp.parseAddressList(header.Get("Reply-To"))
67 email.To = hp.parseAddressList(header.Get("To"))
68 email.Cc = hp.parseAddressList(header.Get("Cc"))
69 email.Bcc = hp.parseAddressList(header.Get("Bcc"))
70 email.Date = hp.parseTime(header.Get("Date"))
71 email.ResentFrom = hp.parseAddressList(header.Get("Resent-From"))
72 email.ResentSender = hp.parseAddress(header.Get("Resent-Sender"))
73 email.ResentTo = hp.parseAddressList(header.Get("Resent-To"))
74 email.ResentCc = hp.parseAddressList(header.Get("Resent-Cc"))
75 email.ResentBcc = hp.parseAddressList(header.Get("Resent-Bcc"))
76 email.ResentMessageID = hp.parseMessageId(header.Get("Resent-Message-ID"))
77 email.MessageID = hp.parseMessageId(header.Get("Message-ID"))
78 email.InReplyTo = hp.parseMessageIdList(header.Get("In-Reply-To"))
79 email.References = hp.parseMessageIdList(header.Get("References"))
80 email.ResentDate = hp.parseTime(header.Get("Resent-Date"))
Dusan Kasanb49ceb62017-04-13 00:00:36 +020081
Dusan Kasane668cf22017-04-18 12:56:51 +020082 if hp.err != nil {
83 err = hp.err
Dusan Kasanb49ceb62017-04-13 00:00:36 +020084 return
85 }
86
Dusan Kasanb49ceb62017-04-13 00:00:36 +020087 //decode whole header for easier access to extra fields
88 //todo: should we decode? aren't only standard fields mime encoded?
89 email.Header, err = decodeHeaderMime(header)
90 if err != nil {
91 return
92 }
93
94 return
95}
96
97func parseContentType(contentTypeHeader string) (contentType string, params map[string]string, err error) {
98 if contentTypeHeader == "" {
Dusan Kasan45ca2642017-04-18 10:39:35 +020099 contentType = contentTypeTextPlain
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200100 return
101 }
102
103 return mime.ParseMediaType(contentTypeHeader)
104}
105
Kevin Chen5dc5bc82018-05-03 22:07:35 -0400106func parseMultipartRelated(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
107 pmr := multipart.NewReader(msg, boundary)
108 for {
109 part, err := pmr.NextPart()
110
111 if err == io.EOF {
112 break
113 } else if err != nil {
114 return textBody, htmlBody, embeddedFiles, err
115 }
116
117 contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
118 if err != nil {
119 return textBody, htmlBody, embeddedFiles, err
120 }
121
122 switch contentType {
123 case contentTypeTextPlain:
avm99963c6b04602021-01-21 01:18:53 +0100124 buf, err := decodeContent(part, part.Header.Get("Content-Transfer-Encoding"))
125 if err != nil {
126 return textBody, htmlBody, embeddedFiles, err
127 }
128
129 ppContent, err := ioutil.ReadAll(buf)
Kevin Chen5dc5bc82018-05-03 22:07:35 -0400130 if err != nil {
131 return textBody, htmlBody, embeddedFiles, err
132 }
133
134 textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
135 case contentTypeTextHtml:
avm99963c6b04602021-01-21 01:18:53 +0100136 buf, err := decodeContent(part, part.Header.Get("Content-Transfer-Encoding"))
137 if err != nil {
138 return textBody, htmlBody, embeddedFiles, err
139 }
140
141 ppContent, err := ioutil.ReadAll(buf)
Kevin Chen5dc5bc82018-05-03 22:07:35 -0400142 if err != nil {
143 return textBody, htmlBody, embeddedFiles, err
144 }
145
146 htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
147 case contentTypeMultipartAlternative:
148 tb, hb, ef, err := parseMultipartAlternative(part, params["boundary"])
149 if err != nil {
150 return textBody, htmlBody, embeddedFiles, err
151 }
152
153 htmlBody += hb
154 textBody += tb
155 embeddedFiles = append(embeddedFiles, ef...)
156 default:
157 if isEmbeddedFile(part) {
158 ef, err := decodeEmbeddedFile(part)
159 if err != nil {
160 return textBody, htmlBody, embeddedFiles, err
161 }
162
163 embeddedFiles = append(embeddedFiles, ef)
164 } else {
165 return textBody, htmlBody, embeddedFiles, fmt.Errorf("Can't process multipart/related inner mime type: %s", contentType)
166 }
167 }
168 }
169
170 return textBody, htmlBody, embeddedFiles, err
171}
172
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200173func parseMultipartAlternative(msg io.Reader, boundary string) (textBody, htmlBody string, embeddedFiles []EmbeddedFile, err error) {
174 pmr := multipart.NewReader(msg, boundary)
175 for {
176 part, err := pmr.NextPart()
177
178 if err == io.EOF {
179 break
180 } else if err != nil {
181 return textBody, htmlBody, embeddedFiles, err
182 }
183
184 contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
Dusan Kasanc661cc02017-04-18 10:51:51 +0200185 if err != nil {
186 return textBody, htmlBody, embeddedFiles, err
187 }
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200188
189 switch contentType {
Dusan Kasan45ca2642017-04-18 10:39:35 +0200190 case contentTypeTextPlain:
avm99963c6b04602021-01-21 01:18:53 +0100191 buf, err := decodeContent(part, part.Header.Get("Content-Transfer-Encoding"))
192 if err != nil {
193 return textBody, htmlBody, embeddedFiles, err
194 }
195
196 ppContent, err := ioutil.ReadAll(buf)
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200197 if err != nil {
198 return textBody, htmlBody, embeddedFiles, err
199 }
200
201 textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
Dusan Kasan45ca2642017-04-18 10:39:35 +0200202 case contentTypeTextHtml:
avm99963c6b04602021-01-21 01:18:53 +0100203 buf, err := decodeContent(part, part.Header.Get("Content-Transfer-Encoding"))
204 if err != nil {
205 return textBody, htmlBody, embeddedFiles, err
206 }
207
208 ppContent, err := ioutil.ReadAll(buf)
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200209 if err != nil {
210 return textBody, htmlBody, embeddedFiles, err
211 }
212
213 htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
Dusan Kasan45ca2642017-04-18 10:39:35 +0200214 case contentTypeMultipartRelated:
Kevin Chen9b9506a2018-05-03 22:17:38 -0400215 tb, hb, ef, err := parseMultipartRelated(part, params["boundary"])
Dusan Kasan1a966482017-04-18 10:45:25 +0200216 if err != nil {
217 return textBody, htmlBody, embeddedFiles, err
218 }
219
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200220 htmlBody += hb
221 textBody += tb
222 embeddedFiles = append(embeddedFiles, ef...)
223 default:
224 if isEmbeddedFile(part) {
225 ef, err := decodeEmbeddedFile(part)
226 if err != nil {
227 return textBody, htmlBody, embeddedFiles, err
228 }
229
230 embeddedFiles = append(embeddedFiles, ef)
231 } else {
Dusan Kasan45ca2642017-04-18 10:39:35 +0200232 return textBody, htmlBody, embeddedFiles, fmt.Errorf("Can't process multipart/alternative inner mime type: %s", contentType)
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200233 }
234 }
235 }
236
237 return textBody, htmlBody, embeddedFiles, err
238}
239
240func parseMultipartMixed(msg io.Reader, boundary string) (textBody, htmlBody string, attachments []Attachment, embeddedFiles []EmbeddedFile, err error) {
241 mr := multipart.NewReader(msg, boundary)
242 for {
243 part, err := mr.NextPart()
244 if err == io.EOF {
245 break
246 } else if err != nil {
247 return textBody, htmlBody, attachments, embeddedFiles, err
248 }
249
250 contentType, params, err := mime.ParseMediaType(part.Header.Get("Content-Type"))
251 if err != nil {
252 return textBody, htmlBody, attachments, embeddedFiles, err
253 }
254
Dusan Kasan45ca2642017-04-18 10:39:35 +0200255 if contentType == contentTypeMultipartAlternative {
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200256 textBody, htmlBody, embeddedFiles, err = parseMultipartAlternative(part, params["boundary"])
257 if err != nil {
258 return textBody, htmlBody, attachments, embeddedFiles, err
259 }
Kevin Chen5dc5bc82018-05-03 22:07:35 -0400260 } else if contentType == contentTypeMultipartRelated {
261 textBody, htmlBody, embeddedFiles, err = parseMultipartRelated(part, params["boundary"])
262 if err != nil {
263 return textBody, htmlBody, attachments, embeddedFiles, err
264 }
Maya Rashisha3803bd2019-06-08 17:53:21 +0300265 } else if contentType == contentTypeTextPlain {
avm99963c6b04602021-01-21 01:18:53 +0100266 buf, err := decodeContent(part, part.Header.Get("Content-Transfer-Encoding"))
267 if err != nil {
268 return textBody, htmlBody, attachments, embeddedFiles, err
269 }
270
271 ppContent, err := ioutil.ReadAll(buf)
Maya Rashisha3803bd2019-06-08 17:53:21 +0300272 if err != nil {
273 return textBody, htmlBody, attachments, embeddedFiles, err
274 }
275
276 textBody += strings.TrimSuffix(string(ppContent[:]), "\n")
k-yomo2e670d92020-04-30 22:42:48 +0900277 } else if contentType == contentTypeTextHtml {
avm99963c6b04602021-01-21 01:18:53 +0100278 buf, err := decodeContent(part, part.Header.Get("Content-Transfer-Encoding"))
279 if err != nil {
280 return textBody, htmlBody, attachments, embeddedFiles, err
281 }
282
283 ppContent, err := ioutil.ReadAll(buf)
k-yomo2e670d92020-04-30 22:42:48 +0900284 if err != nil {
285 return textBody, htmlBody, attachments, embeddedFiles, err
286 }
287
288 htmlBody += strings.TrimSuffix(string(ppContent[:]), "\n")
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200289 } else if isAttachment(part) {
290 at, err := decodeAttachment(part)
291 if err != nil {
292 return textBody, htmlBody, attachments, embeddedFiles, err
293 }
294
295 attachments = append(attachments, at)
296 } else {
Dusan Kasan45ca2642017-04-18 10:39:35 +0200297 return textBody, htmlBody, attachments, embeddedFiles, fmt.Errorf("Unknown multipart/mixed nested mime type: %s", contentType)
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200298 }
299 }
300
301 return textBody, htmlBody, attachments, embeddedFiles, err
Dusan Kasan17e497e2017-04-10 22:44:22 +0200302}
303
Dusan Kasanf4376a62017-05-23 21:03:55 +0200304func decodeMimeSentence(s string) string {
Dusan Kasan17e497e2017-04-10 22:44:22 +0200305 result := []string{}
306 ss := strings.Split(s, " ")
307
308 for _, word := range ss {
309 dec := new(mime.WordDecoder)
310 w, err := dec.Decode(word)
311 if err != nil {
312 if len(result) == 0 {
313 w = word
314 } else {
315 w = " " + word
316 }
317 }
318
319 result = append(result, w)
320 }
321
Dusan Kasanf4376a62017-05-23 21:03:55 +0200322 return strings.Join(result, "")
Dusan Kasan17e497e2017-04-10 22:44:22 +0200323}
324
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200325func decodeHeaderMime(header mail.Header) (mail.Header, error) {
Dusan Kasan17e497e2017-04-10 22:44:22 +0200326 parsedHeader := map[string][]string{}
327
328 for headerName, headerData := range header {
329
330 parsedHeaderData := []string{}
331 for _, headerValue := range headerData {
Dusan Kasanf4376a62017-05-23 21:03:55 +0200332 parsedHeaderData = append(parsedHeaderData, decodeMimeSentence(headerValue))
Dusan Kasan17e497e2017-04-10 22:44:22 +0200333 }
334
335 parsedHeader[headerName] = parsedHeaderData
336 }
337
338 return mail.Header(parsedHeader), nil
339}
340
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200341func isEmbeddedFile(part *multipart.Part) bool {
342 return part.Header.Get("Content-Transfer-Encoding") != ""
343}
344
345func decodeEmbeddedFile(part *multipart.Part) (ef EmbeddedFile, err error) {
Dusan Kasanf4376a62017-05-23 21:03:55 +0200346 cid := decodeMimeSentence(part.Header.Get("Content-Id"))
Dusan Kasan428369f2020-02-24 00:47:31 +0100347 decoded, err := decodeContent(part, part.Header.Get("Content-Transfer-Encoding"))
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200348 if err != nil {
349 return
350 }
351
352 ef.CID = strings.Trim(cid, "<>")
353 ef.Data = decoded
354 ef.ContentType = part.Header.Get("Content-Type")
355
356 return
357}
358
359func isAttachment(part *multipart.Part) bool {
360 return part.FileName() != ""
361}
362
363func decodeAttachment(part *multipart.Part) (at Attachment, err error) {
Dusan Kasanf4376a62017-05-23 21:03:55 +0200364 filename := decodeMimeSentence(part.FileName())
Dusan Kasan428369f2020-02-24 00:47:31 +0100365 decoded, err := decodeContent(part, part.Header.Get("Content-Transfer-Encoding"))
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200366 if err != nil {
367 return
368 }
369
370 at.Filename = filename
371 at.Data = decoded
372 at.ContentType = strings.Split(part.Header.Get("Content-Type"), ";")[0]
373
374 return
375}
376
Dusan Kasan428369f2020-02-24 00:47:31 +0100377func decodeContent(content io.Reader, encoding string) (io.Reader, error) {
378 switch encoding {
avm99963c6b04602021-01-21 01:18:53 +0100379 case "base64", "Base64":
Dusan Kasan428369f2020-02-24 00:47:31 +0100380 decoded := base64.NewDecoder(base64.StdEncoding, content)
381 b, err := ioutil.ReadAll(decoded)
382 if err != nil {
383 return nil, err
384 }
385
386 return bytes.NewReader(b), nil
Dusan Kasan3325e732020-04-04 11:46:04 +0200387 case "7bit":
388 dd, err := ioutil.ReadAll(content)
389 if err != nil {
390 return nil, err
391 }
392
393 return bytes.NewReader(dd), nil
Dusan Kasan428369f2020-02-24 00:47:31 +0100394 case "":
395 return content, nil
396 default:
397 return nil, fmt.Errorf("unknown encoding: %s", encoding)
398 }
399}
400
Dusan Kasane668cf22017-04-18 12:56:51 +0200401type headerParser struct {
402 header *mail.Header
Dusan Kasanb974c632017-04-18 12:58:42 +0200403 err error
Dusan Kasane668cf22017-04-18 12:56:51 +0200404}
405
406func (hp headerParser) parseAddress(s string) (ma *mail.Address) {
407 if hp.err != nil {
408 return nil
409 }
410
411 if strings.Trim(s, " \n") != "" {
412 ma, hp.err = mail.ParseAddress(s)
413
414 return ma
415 }
416
417 return nil
418}
419
420func (hp headerParser) parseAddressList(s string) (ma []*mail.Address) {
421 if hp.err != nil {
422 return
423 }
424
425 if strings.Trim(s, " \n") != "" {
426 ma, hp.err = mail.ParseAddressList(s)
427 return
428 }
429
430 return
431}
432
433func (hp headerParser) parseTime(s string) (t time.Time) {
Dusan Kasanb974c632017-04-18 12:58:42 +0200434 if hp.err != nil || s == "" {
Dusan Kasane668cf22017-04-18 12:56:51 +0200435 return
436 }
437
Dusan Kasan88226cf2020-04-04 11:13:06 +0200438 formats := []string{
439 time.RFC1123Z,
440 "Mon, 2 Jan 2006 15:04:05 -0700",
441 time.RFC1123Z + " (MST)",
442 "Mon, 2 Jan 2006 15:04:05 -0700 (MST)",
Dusan Kasane668cf22017-04-18 12:56:51 +0200443 }
444
Dusan Kasan88226cf2020-04-04 11:13:06 +0200445 for _, format := range formats {
446 t, hp.err = time.Parse(format, s)
447 if hp.err == nil {
448 return
449 }
450 }
Dusan Kasane668cf22017-04-18 12:56:51 +0200451
452 return
453}
454
455func (hp headerParser) parseMessageId(s string) string {
456 if hp.err != nil {
457 return ""
458 }
459
460 return strings.Trim(s, "<> ")
461}
462
463func (hp headerParser) parseMessageIdList(s string) (result []string) {
464 if hp.err != nil {
465 return
466 }
467
468 for _, p := range strings.Split(s, " ") {
469 if strings.Trim(p, " \n") != "" {
470 result = append(result, hp.parseMessageId(p))
471 }
472 }
473
474 return
475}
476
Dusan Kasan1a966482017-04-18 10:45:25 +0200477// Attachment with filename, content type and data (as a io.Reader)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200478type Attachment struct {
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200479 Filename string
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200480 ContentType string
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200481 Data io.Reader
Dusan Kasan17e497e2017-04-10 22:44:22 +0200482}
483
Dusan Kasan1a966482017-04-18 10:45:25 +0200484// EmbeddedFile with content id, content type and data (as a io.Reader)
Dusan Kasan17e497e2017-04-10 22:44:22 +0200485type EmbeddedFile struct {
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200486 CID string
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200487 ContentType string
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200488 Data io.Reader
Dusan Kasan17e497e2017-04-10 22:44:22 +0200489}
490
Dusan Kasan1a966482017-04-18 10:45:25 +0200491// Email with fields for all the headers defined in RFC5322 with it's attachments and
Dusan Kasan17e497e2017-04-10 22:44:22 +0200492type Email struct {
493 Header mail.Header
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200494
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200495 Subject string
496 Sender *mail.Address
497 From []*mail.Address
498 ReplyTo []*mail.Address
499 To []*mail.Address
500 Cc []*mail.Address
501 Bcc []*mail.Address
502 Date time.Time
503 MessageID string
504 InReplyTo []string
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200505 References []string
506
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200507 ResentFrom []*mail.Address
508 ResentSender *mail.Address
509 ResentTo []*mail.Address
510 ResentDate time.Time
511 ResentCc []*mail.Address
512 ResentBcc []*mail.Address
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200513 ResentMessageID string
514
Dusan Kasan428369f2020-02-24 00:47:31 +0100515 ContentType string
516 Content io.Reader
517
Dusan Kasan17e497e2017-04-10 22:44:22 +0200518 HTMLBody string
519 TextBody string
Dusan Kasanb49ceb62017-04-13 00:00:36 +0200520
Dusan Kasan4595dfe2017-04-13 00:38:24 +0200521 Attachments []Attachment
Dusan Kasan17e497e2017-04-10 22:44:22 +0200522 EmbeddedFiles []EmbeddedFile
avm99963c6b04602021-01-21 01:18:53 +0100523}