package main // TODO: use https://github.com/domodwyer/mailyak as a base lib import ( "bytes" "encoding/base64" "errors" "fmt" "log" "math/rand" "net/mail" "strings" ) type EmailHeaders map[string]string type EmailConfig struct { From string To []string Subject string } type EmailCtx struct { Headers EmailHeaders BodyPlain string BodyHtml string } const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func RandStringBytes(n int) string { b := make([]byte, n) for i := range b { b[i] = letterBytes[rand.Intn(len(letterBytes))] } return string(b) } func (headers EmailHeaders) String() string { var buffer bytes.Buffer for k, v := range headers { buffer.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)) } return buffer.String() } func NewEmail() *EmailCtx { email := EmailCtx{} email.Headers = make(EmailHeaders) return &email } func encodeRFC2047(text string) string { // use mail's rfc2047 to encode any string addr := mail.Address{Name: text, Address: ""} return strings.Trim(addr.String(), " \"<@>") } func (email *EmailCtx) SetHeaders(config EmailConfig) { email.Headers["Return-Path"] = config.From email.Headers["From"] = config.From if len(config.To) < 1 { errmsg := "EMAIL_TO must contain at least one value" log.Panic(errors.New(errmsg)) } email.Headers["To"] = config.To[0] if len(config.To) > 1 { email.Headers["Cc"] = strings.Join(config.To[1:], ",") } email.Headers["Subject"] = encodeRFC2047(config.Subject) email.Headers["Content-Transfer-Encoding"] = "quoted-printable" email.Headers["MIME-Version"] = "1.0" return } func (email *EmailCtx) SetBody(html string, plain string) { email.BodyPlain = plain email.BodyHtml = html return } func (email *EmailCtx) String() string { var buffer bytes.Buffer mixBoundary := RandStringBytes(16) altBoundary := RandStringBytes(16) buffer.WriteString(email.Headers.String()) buffer.WriteString(fmt.Sprintf("Content-Type: multipart/mixed;\r\n boundary=\"%s\"\r\n", mixBoundary)) buffer.WriteString("\r\n") buffer.WriteString(fmt.Sprintf("--%s\r\n", mixBoundary)) buffer.WriteString(fmt.Sprintf("Content-Type: multipart/alternative;\r\n boundary=\"%s\"\r\n", altBoundary)) buffer.WriteString("\r\n") buffer.WriteString(fmt.Sprintf("--%s\r\n", altBoundary)) buffer.WriteString(fmt.Sprintf("Content-Type: text/plain; charset=\"utf-8\"\r\n")) buffer.WriteString(fmt.Sprintf("Content-Transfer-Encoding: base64\r\n")) buffer.WriteString("\r\n") buffer.WriteString(base64.StdEncoding.EncodeToString([]byte(email.BodyPlain))) buffer.WriteString("\r\n") buffer.WriteString("\r\n") buffer.WriteString(fmt.Sprintf("--%s\r\n", altBoundary)) buffer.WriteString(fmt.Sprintf("Content-Type: text/html; charset=\"utf-8\"\r\n")) buffer.WriteString(fmt.Sprintf("Content-Transfer-Encoding: base64\r\n")) buffer.WriteString("\r\n") buffer.WriteString(base64.StdEncoding.EncodeToString([]byte(email.BodyHtml))) buffer.WriteString("\r\n") buffer.WriteString("\r\n") buffer.WriteString(fmt.Sprintf("--%s--\r\n", altBoundary)) buffer.WriteString("\r\n") buffer.WriteString(fmt.Sprintf("--%s--\r\n", mixBoundary)) buffer.WriteString("\r\n") return buffer.String() }