// Package mail provides IMAP reading and RFC822 message parsing. package mail import ( "bytes" "io" "strings" "github.com/emersion/go-message/mail" ) type Header struct { UID uint32 From string To string Subject string Date string MessageID string HasAttachments bool } type Attachment struct { Name string Size int MIME string Content []byte } type Message struct { Header Header BodyText string Attachments []Attachment } func readHeader(mr *mail.Reader, uid uint32) Header { h := Header{UID: uid} hd := mr.Header h.Subject, _ = hd.Subject() if addrs, err := hd.AddressList("From"); err == nil && len(addrs) > 0 { h.From = addrs[0].String() } if addrs, err := hd.AddressList("To"); err == nil && len(addrs) > 0 { h.To = addrs[0].String() } if d, err := hd.Date(); err == nil { h.Date = d.UTC().Format("Mon, 02 Jan 2006 15:04:05 -0700") } if msgID, err := hd.MessageID(); err == nil { h.MessageID = msgID } else { h.MessageID = strings.Trim(hd.Get("Message-Id"), "<> ") } return h } // ParseMessage decodes the full message including attachment contents. func ParseMessage(uid uint32, raw []byte) (Message, error) { mr, err := mail.CreateReader(bytes.NewReader(raw)) if err != nil { return Message{}, err } m := Message{Header: readHeader(mr, uid)} for { part, err := mr.NextPart() if err == io.EOF { break } if err != nil { return Message{}, err } switch hdr := part.Header.(type) { case *mail.InlineHeader: ct, _, _ := hdr.ContentType() if strings.HasPrefix(ct, "text/plain") && m.BodyText == "" { b, _ := io.ReadAll(part.Body) m.BodyText = string(b) } case *mail.AttachmentHeader: name, _ := hdr.Filename() ct, _, _ := hdr.ContentType() b, _ := io.ReadAll(part.Body) m.Attachments = append(m.Attachments, Attachment{ Name: name, Size: len(b), MIME: ct, Content: b, }) } } m.Header.HasAttachments = len(m.Attachments) > 0 return m, nil } // ParseHeaderOnly decodes headers and detects attachments without keeping bodies. func ParseHeaderOnly(uid uint32, raw []byte) (Header, error) { m, err := ParseMessage(uid, raw) if err != nil { return Header{}, err } return m.Header, nil }