func extractBoundary(contentType string) string {
	_, params, err := mime.ParseMediaType(contentType)
	if err == nil {
		return params["boundary"]
	}
	return ""
}
extractBoundary extract boundary string in contentType.
It returns empty string if no valid boundary found
package data
import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"io"
	"log"
	"mime"
	"strings"
	"time"
)
var LogHandler func(message string, args ...interface{})
func logf(message string, args ...interface{}) {
	if LogHandler != nil {
		LogHandler(message, args...)
	} else {
		log.Printf(message, args...)
	}
}
type MessageID string
func NewMessageID(hostname string) (MessageID, error) {
	size := 32
	rb := make([]byte, size)
	_, err := rand.Read(rb)
	if err != nil {
		return MessageID(""), err
	}
	rs := base64.URLEncoding.EncodeToString(rb)
	return MessageID(rs + "@" + hostname), nil
}
type Messages []Message
type Message struct {
	ID      MessageID
	From    *Path
	To      []*Path
	Content *Content
	Created time.Time
	MIME    *MIMEBody  
	Raw     *SMTPMessage
}
type Path struct {
	Relays  []string
	Mailbox string
	Domain  string
	Params  string
}
type Content struct {
	Headers map[string][]string
	Body    string
	Size    int
	MIME    *MIMEBody
}
type SMTPMessage struct {
	From string
	To   []string
	Data string
	Helo string
}
type MIMEBody struct {
	Parts []*Content
}
func (m *SMTPMessage) Parse(hostname string) *Message {
	var arr []*Path
	for _, path := range m.To {
		arr = append(arr, PathFromString(path))
	}
	id, _ := NewMessageID(hostname)
	msg := &Message{
		ID:      id,
		From:    PathFromString(m.From),
		To:      arr,
		Content: ContentFromString(m.Data),
		Created: time.Now(),
		Raw:     m,
	}
	if msg.Content.IsMIME() {
		logf("Parsing MIME body")
		msg.MIME = msg.Content.ParseMIMEBody()
	}
	var hasMessageID bool
	var receivedHeaderName string
	var returnPathHeaderName string
	for k := range msg.Content.Headers {
		if strings.ToLower(k) == "message-id" {
			hasMessageID = true
			continue
		}
		if strings.ToLower(k) == "received" {
			receivedHeaderName = k
			continue
		}
		if strings.ToLower(k) == "return-path" {
			returnPathHeaderName = k
			continue
		}
	}
	if !hasMessageID {
		msg.Content.Headers["Message-ID"] = []string{string(id)}
	}
	if len(receivedHeaderName) > 0 {
		msg.Content.Headers[receivedHeaderName] = append(msg.Content.Headers[receivedHeaderName], "from "+m.Helo+" by "+hostname+" (MailHog)\r\n          id "+string(id)+"; "+time.Now().Format(time.RFC1123Z))
	} else {
		msg.Content.Headers["Received"] = []string{"from " + m.Helo + " by " + hostname + " (MailHog)\r\n          id " + string(id) + "; " + time.Now().Format(time.RFC1123Z)}
	}
	if len(returnPathHeaderName) > 0 {
		msg.Content.Headers[returnPathHeaderName] = append(msg.Content.Headers[returnPathHeaderName], "<"+m.From+">")
	} else {
		msg.Content.Headers["Return-Path"] = []string{"<" + m.From + ">"}
	}
	return msg
}
func (m *SMTPMessage) Bytes() io.Reader {
	var b = new(bytes.Buffer)
	b.WriteString("HELO:<" + m.Helo + ">\r\n")
	b.WriteString("FROM:<" + m.From + ">\r\n")
	for _, t := range m.To {
		b.WriteString("TO:<" + t + ">\r\n")
	}
	b.WriteString("\r\n")
	b.WriteString(m.Data)
	return b
}
func FromBytes(b []byte) *SMTPMessage {
	msg := &SMTPMessage{}
	var headerDone bool
	for _, l := range strings.Split(string(b), "\n") {
		if !headerDone {
			if strings.HasPrefix(l, "HELO:<") {
				l = strings.TrimPrefix(l, "HELO:<")
				l = strings.TrimSuffix(l, ">\r")
				msg.Helo = l
				continue
			}
			if strings.HasPrefix(l, "FROM:<") {
				l = strings.TrimPrefix(l, "FROM:<")
				l = strings.TrimSuffix(l, ">\r")
				msg.From = l
				continue
			}
			if strings.HasPrefix(l, "TO:<") {
				l = strings.TrimPrefix(l, "TO:<")
				l = strings.TrimSuffix(l, ">\r")
				msg.To = append(msg.To, l)
				continue
			}
			if strings.TrimSpace(l) == "" {
				headerDone = true
				continue
			}
		}
		msg.Data += l + "\n"
	}
	return msg
}
func (m *Message) Bytes() io.Reader {
	var b = new(bytes.Buffer)
	for k, vs := range m.Content.Headers {
		for _, v := range vs {
			b.WriteString(k + ": " + v + "\r\n")
		}
	}
	b.WriteString("\r\n")
	b.WriteString(m.Content.Body)
	return b
}
func (content *Content) IsMIME() bool {
	header, ok := content.Headers["Content-Type"]
	if !ok {
		return false
	}
	return strings.HasPrefix(header[0], "multipart/")
}
func (content *Content) ParseMIMEBody() *MIMEBody {
	var parts []*Content
	if hdr, ok := content.Headers["Content-Type"]; ok {
		if len(hdr) > 0 {
			boundary := extractBoundary(hdr[0])
			var p []string
			if len(boundary) > 0 {
				p = strings.Split(content.Body, "--"+boundary)
				logf("Got boundary: %s", boundary)
			} else {
				logf("Boundary not found: %s", hdr[0])
			}
			for _, s := range p {
				if len(s) > 0 {
					part := ContentFromString(strings.Trim(s, "\r\n"))
					if part.IsMIME() {
						logf("Parsing inner MIME body")
						part.MIME = part.ParseMIMEBody()
					}
					parts = append(parts, part)
				}
			}
		}
	}
	return &MIMEBody{
		Parts: parts,
	}
}
func PathFromString(path string) *Path {
	var relays []string
	email := path
	if strings.Contains(path, ":") {
		x := strings.SplitN(path, ":", 2)
		r, e := x[0], x[1]
		email = e
		relays = strings.Split(r, ",")
	}
	mailbox, domain := "", ""
	if strings.Contains(email, "@") {
		x := strings.SplitN(email, "@", 2)
		mailbox, domain = x[0], x[1]
	} else {
		mailbox = email
	}
	return &Path{
		Relays:  relays,
		Mailbox: mailbox,
		Domain:  domain,
		Params:  "",  
	}
}
func ContentFromString(data string) *Content {
	logf("Parsing Content from string: '%s'", data)
	x := strings.SplitN(data, "\r\n\r\n", 2)
	h := make(map[string][]string, 0)
	if len(x) == 2 {
		headers, body := x[0], x[1]
		hdrs := strings.Split(headers, "\r\n")
		var lastHdr = ""
		for _, hdr := range hdrs {
			if lastHdr != "" && (strings.HasPrefix(hdr, " ") || strings.HasPrefix(hdr, "\t")) {
				h[lastHdr][len(h[lastHdr])-1] = h[lastHdr][len(h[lastHdr])-1] + hdr
			} else if strings.Contains(hdr, ": ") {
				y := strings.SplitN(hdr, ": ", 2)
				key, value := y[0], y[1]
				h[key] = []string{value}
				lastHdr = key
			} else if len(hdr) > 0 {
				logf("Found invalid header: '%s'", hdr)
			}
		}
		return &Content{
			Size:    len(data),
			Headers: h,
			Body:    body,
		}
	}
	return &Content{
		Size:    len(data),
		Headers: h,
		Body:    x[0],
	}
}